From 91f1a82f0b0922b8b0afb6bb320d817c4a8fd047 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:39:00 -0400 Subject: [PATCH 001/370] build(semantic-release): create new release branches 'adm' and 'detections' --- .releaserc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.releaserc b/.releaserc index 11aa5597..7c71b66b 100644 --- a/.releaserc +++ b/.releaserc @@ -12,6 +12,14 @@ { "name": "alpha", "prerelease": true + }, + { + "name": "detections", + "prerelease": true + }, + { + "name": "adm", + "prerelease": true } ], "plugins": [ From 5a7dfcfdfd76dc2ed733935cfec272d58f054a33 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:39:00 -0400 Subject: [PATCH 002/370] build(semantic-release): create new release branches 'adm' and 'detections' --- .releaserc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.releaserc b/.releaserc index 11aa5597..7c71b66b 100644 --- a/.releaserc +++ b/.releaserc @@ -12,6 +12,14 @@ { "name": "alpha", "prerelease": true + }, + { + "name": "detections", + "prerelease": true + }, + { + "name": "adm", + "prerelease": true } ], "plugins": [ From 910797648d845ea99609d1d9276ecdaae23759ea Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 17 Jul 2025 10:34:01 -0400 Subject: [PATCH 003/370] feat: new endpoint for validation --- app/api/definitions/paths/assets-paths.yml | 29 +++++ app/dtos/index.js | 8 ++ app/dtos/workflow-states.js | 70 ++++++++++ app/middleware/field-schemas.js | 38 ++++++ .../tactic-validation-middleware.js | 121 ++++++++++++++++++ app/routes/tactics-routes.js | 8 +- 6 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 app/dtos/index.js create mode 100644 app/dtos/workflow-states.js create mode 100644 app/middleware/field-schemas.js create mode 100644 app/middleware/tactic-validation-middleware.js diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index 5cec7e1f..06744b9a 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -290,3 +290,32 @@ paths: description: 'The asset was successfully deleted.' '404': description: 'An asset with the requested STIX id and modified date was not found.' + + /api/assets/validate: + post: + summary: 'Validate an asset or a field of an asset' + operationId: 'asset-validate' + description: | + This endpoint validates an asset object or a field of an asset without creating or updating it. + tags: + - 'Assets' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/assets.yml#/components/schemas/asset' + responses: + '200': + description: 'Validation result.' + content: + application/json: + schema: + type: object + properties: + valid: + type: boolean + errors: + type: array + items: + type: string diff --git a/app/dtos/index.js b/app/dtos/index.js new file mode 100644 index 00000000..af74c6ef --- /dev/null +++ b/app/dtos/index.js @@ -0,0 +1,8 @@ +'use strict'; + +// Export all DTOs from this directory +const workflowStates = require('./workflow-states'); + +module.exports = { + workflowStates, +}; diff --git a/app/dtos/workflow-states.js b/app/dtos/workflow-states.js new file mode 100644 index 00000000..5bdd7f2c --- /dev/null +++ b/app/dtos/workflow-states.js @@ -0,0 +1,70 @@ +'use strict'; + +/** + * Enum for ATT&CK object workflow states + * @readonly + * @enum {string} + */ +const WorkflowStates = { + /** Initial state for objects being developed */ + WORK_IN_PROGRESS: 'work-in-progress', + + /** State for objects ready for review by leads */ + AWAITING_REVIEW: 'awaiting-review', + + /** State for objects that have been reviewed and approved */ + REVIEWED: 'reviewed', + + /** State for objects that should not be modified (typically imported/system objects) */ + STATIC: 'static', +}; + +/** + * Check if a state transition is valid + * @param {string} fromState - Current workflow state + * @param {string} toState - Target workflow state + * @returns {boolean} - Whether the transition is valid + */ +const isValidStateTransition = (fromState, toState) => { + // If states are the same, it's not a transition + if (fromState === toState) { + return false; + } + + // Define allowed transitions + const allowedTransitions = { + [WorkflowStates.WORK_IN_PROGRESS]: [WorkflowStates.AWAITING_REVIEW], + [WorkflowStates.AWAITING_REVIEW]: [ + WorkflowStates.WORK_IN_PROGRESS, // Return to work-in-progress if changes needed + WorkflowStates.REVIEWED, // Approve after review + ], + [WorkflowStates.REVIEWED]: [ + WorkflowStates.WORK_IN_PROGRESS, // Reopen for edits if needed + ], + [WorkflowStates.STATIC]: [], // Static objects cannot transition to other states + }; + + // Check if the transition is allowed + return allowedTransitions[fromState]?.includes(toState) || false; +}; + +/** + * Check if an object is transitioning from work-in-progress to awaiting-review + * @param {Object} newObject - The updated object state + * @param {Object} oldObject - The previous object state + * @returns {boolean} - True if the object is transitioning to awaiting-review + */ +const isTransitioningToReview = (newObject, oldObject) => { + const newState = newObject?.workspace?.workflow?.state; + const oldState = oldObject?.workspace?.workflow?.state; + + return ( + newState === WorkflowStates.AWAITING_REVIEW && oldState === WorkflowStates.WORK_IN_PROGRESS + ); +}; + +module.exports = { + WorkflowStates, + isValidStateTransition, + isTransitioningToReview, +}; diff --git a/app/middleware/field-schemas.js b/app/middleware/field-schemas.js new file mode 100644 index 00000000..20f8a2fe --- /dev/null +++ b/app/middleware/field-schemas.js @@ -0,0 +1,38 @@ +'use strict'; + +const { + nameSchema, + tacticIdSchema, + descriptionSchema, + externalReferencesSchema, + objectMarkingRefsSchema, + xMitreDomainsSchema, + xMitreShortNameSchema, + xMitreModifiedByRefSchema, + stixTypeSchema, + xMitreContributorsSchema, + xMitreAttackSpecVersionSchema, + xMitreVersionSchema, + xMitreOldAttackIdSchema, + xMitreDeprecatedSchema, +} = require('@mitre-attack/attack-data-model'); + +const fieldSchemas = { + name: nameSchema, + id: tacticIdSchema, + type: stixTypeSchema, + x_mitre_version: xMitreVersionSchema, + description: descriptionSchema, + created_by_ref: descriptionSchema, + external_references: externalReferencesSchema, + object_marking_refs: objectMarkingRefsSchema, + x_mitre_domains: xMitreDomainsSchema, + x_mitre_attack_spec_version: xMitreAttackSpecVersionSchema, + x_mitre_shortname: xMitreShortNameSchema, + x_mitre_modified_by_ref: xMitreModifiedByRefSchema, + x_mitre_contributors: xMitreContributorsSchema, + x_mitre_old_attack_id: xMitreOldAttackIdSchema, + x_mitre_deprecated: xMitreDeprecatedSchema, +}; + +module.exports = fieldSchemas; diff --git a/app/middleware/tactic-validation-middleware.js b/app/middleware/tactic-validation-middleware.js new file mode 100644 index 00000000..df5c8ce9 --- /dev/null +++ b/app/middleware/tactic-validation-middleware.js @@ -0,0 +1,121 @@ +'use strict'; + +const { tacticSchema } = require('@mitre-attack/attack-data-model'); +const fieldSchemas = require('./field-schemas'); +const logger = require('../lib/logger'); +const tacticsService = require('../services/tactics-service'); +const { workflowStates } = require('../dtos'); + +/** + * Middleware to validate a tactic object against the ATT&CK Data Model + * when transitioning from 'work-in-progress' to 'awaiting-review' + */ +function hasValue(field) { + return ( + field !== undefined && + field !== null && + field !== '' && + !(Array.isArray(field) && field.length === 0) + ); +} + +module.exports = async function validateTacticForSpecCompliance(req, res, next) { + const tacticData = req.body; + + // Skip validation if tactic is not in 'awaiting-review' state + if ( + !tacticData?.workspace?.workflow?.state || + (tacticData.workspace.workflow.state !== workflowStates.WorkflowStates.AWAITING_REVIEW && + tacticData.workspace.workflow.state !== workflowStates.WorkflowStates.REVIEWED) + ) { + try { + // Validate the tactic object using the ATT&CK Data Model + logger.debug(`Validating tactic ${tacticData.stix.id} against ATT&CK Data Model`); + + for (const [key, value] of Object.entries(tacticData.stix)) { + if (fieldSchemas[key] && hasValue(value) && key != 'x_mitre_shortname') { + console.log(`Key: ${key}, Value: ${value}`); + + // Dynamically validate the field using its schema + const schema = fieldSchemas[key]; + const validationResult = schema.safeParse(value); + + if (!validationResult.success) { + // Format Zod errors into a more readable structure + const errors = validationResult.error.errors.map((err) => ({ + path: err.path.join('.'), + message: err.message, + code: err.code, + })); + + logger.warn( + `Validation failed for field "${key}" in tactic ${tacticData.stix.id}: ${JSON.stringify(errors)}`, + ); + + return res.status(400).json({ + error: 'Validation Error', + message: `Field "${key}" does not meet ATT&CK Data Model requirements`, + details: errors, + }); + } + } + } + return next(); + } catch (err) { + logger.error(`Tactic validation error: ${err.message}`); + return res.status(500).send('Unable to validate tactic. Server error.'); + } + } + + try { + // Check if we're updating an existing tactic + const isUpdate = req.params.stixId && req.params.modified; + + if (isUpdate) { + const existingTactic = await tacticsService.retrieveVersionById( + req.params.stixId, + req.params.modified, + ); + + // Only validate if transitioning from 'work-in-progress' to 'awaiting-review' + if (!workflowStates.isTransitioningToReview(tacticData, existingTactic)) { + return next(); + } + } + + // Validate the tactic object using the ATT&CK Data Model + logger.debug(`Validating tactic ${tacticData.stix.id} against ATT&CK Data Model`); + + // Use the partial schema to validate only the fields provided in req.body.stix + const validationResult = tacticSchema.safeParse(tacticData.stix); + + // Use the ATT&CK Data Model's tactic schema to validate + // This uses Zod for validation with robust error reporting + // const validationResult = tacticSchema.safeParse(tacticData.stix); + + if (!validationResult.success) { + // Format Zod errors into a more readable structure + const errors = validationResult.error.errors.map((err) => ({ + path: err.path.join('.'), + message: err.message, + code: err.code, + })); + + logger.warn(`Tactic validation failed for ${tacticData.stix.id}: ${JSON.stringify(errors)}`); + + return res.status(400).json({ + error: 'Validation Error', + message: 'Tactic does not meet ATT&CK Data Model requirements', + details: errors, + }); + } + + logger.debug( + `Tactic ${tacticData.stix.id} successfully validated against ATT&CK Data Model schema`, + ); + return next(); + } catch (err) { + logger.error(`Tactic validation error: ${err.message}`); + return res.status(500).send('Unable to validate tactic. Server error.'); + } +}; diff --git a/app/routes/tactics-routes.js b/app/routes/tactics-routes.js index 0e7b6080..7ebe9e02 100644 --- a/app/routes/tactics-routes.js +++ b/app/routes/tactics-routes.js @@ -5,6 +5,7 @@ const express = require('express'); const tacticsController = require('../controllers/tactics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const validateTacticForSpecCompliance = require('../middleware/tactic-validation-middleware'); const router = express.Router(); @@ -15,7 +16,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), tacticsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateTacticForSpecCompliance, + tacticsController.create, + ); router .route('/tactics/:stixId') From 33ad9bf78db33967d87775b179fc57eb687aa174 Mon Sep 17 00:00:00 2001 From: adpare Date: Sat, 9 Aug 2025 11:32:11 -0400 Subject: [PATCH 004/370] feat: validate api setup --- app/api/definitions/openapi.yml | 4 + app/api/definitions/paths/validate-paths.yml | 50 +++++++++++ app/controllers/validate-controller.js | 38 ++++++++ .../tactic-validation-middleware.js | 56 ++++++------ app/routes/validate-routes.js | 15 ++++ app/services/validate-service.js | 56 ++++++++++++ package-lock.json | 88 ++++++++++++++++++- package.json | 1 + 8 files changed, 276 insertions(+), 32 deletions(-) create mode 100644 app/api/definitions/paths/validate-paths.yml create mode 100644 app/controllers/validate-controller.js create mode 100644 app/routes/validate-routes.js create mode 100644 app/services/validate-service.js diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index fc0bbdd8..e7437fd9 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -324,3 +324,7 @@ paths: /api/health/status: $ref: 'paths/health-paths.yml#/paths/~1api~1health~1status' + + # Validate + /api/validate: + $ref: 'paths/validate-paths.yml#/paths/~1api~1validate' diff --git a/app/api/definitions/paths/validate-paths.yml b/app/api/definitions/paths/validate-paths.yml new file mode 100644 index 00000000..c18df7a2 --- /dev/null +++ b/app/api/definitions/paths/validate-paths.yml @@ -0,0 +1,50 @@ +paths: + /api/validate: + post: + summary: 'Validate a STIX object' + operationId: 'validate-stix-object' + description: | + Validates a STIX object against the schema specified in the request body `type` field. + Returns detailed error information as ZodError objects. + tags: + - 'Validation' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - stix + properties: + type: + type: string + description: Valid STIX type (e.g., "attack-pattern", "intrustion-set", etc.) + stix: + type: object + description: The STIX object to validate + responses: + '200': + description: 'Validation result' + content: + application/json: + schema: + type: object + properties: + errors: + type: array + items: + type: object + properties: + code: + type: string + example: custom + path: + type: array + items: + type: string + message: + type: string + '400': + description: 'Invalid request' diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js new file mode 100644 index 00000000..100b2b57 --- /dev/null +++ b/app/controllers/validate-controller.js @@ -0,0 +1,38 @@ +'use strict'; + +const logger = require('../lib/logger'); +const ValidationService = require('../services/validate-service'); +const validateService = new ValidationService(); + +exports.validate = async function (req, res) { + try { + const { type, stix } = req.body || {}; + + // Basic request validation + if (typeof type !== 'string' || typeof stix !== 'object' || stix === null || Array.isArray(stix)) { + logger.warn('Invalid request body for /api/validate'); + return res.status(400).json({ + errors: [{ + code: 'invalid_request', + path: [], + message: 'Request body must have a string "type" and an object "stix".' + }] + }); + } + + // Validate the STIX object using the instance method + const result = validateService.validate(type, stix); + + if (result.errors.length && result.errors[0].code === 'unknown_type') { + logger.warn(`Unknown STIX type: ${type}`); + return res.status(400).json(result); + } + + logger.debug(`Validation completed for type: ${type}`); + return res.status(200).json(result); + + } catch (err) { + logger.error('Failed to validate STIX object: ' + err); + return res.status(500).send('Unable to validate STIX object. Server error.'); + } +}; diff --git a/app/middleware/tactic-validation-middleware.js b/app/middleware/tactic-validation-middleware.js index df5c8ce9..77aa765e 100644 --- a/app/middleware/tactic-validation-middleware.js +++ b/app/middleware/tactic-validation-middleware.js @@ -1,7 +1,6 @@ 'use strict'; const { tacticSchema } = require('@mitre-attack/attack-data-model'); -const fieldSchemas = require('./field-schemas'); const logger = require('../lib/logger'); const tacticsService = require('../services/tactics-service'); const { workflowStates } = require('../dtos'); @@ -19,6 +18,12 @@ function hasValue(field) { ); } +function filterObject(obj) { + return Object.fromEntries( + Object.entries(obj).filter(([key, value]) => hasValue(value)) + ); +} + module.exports = async function validateTacticForSpecCompliance(req, res, next) { const tacticData = req.body; @@ -31,34 +36,25 @@ module.exports = async function validateTacticForSpecCompliance(req, res, next) try { // Validate the tactic object using the ATT&CK Data Model logger.debug(`Validating tactic ${tacticData.stix.id} against ATT&CK Data Model`); - - for (const [key, value] of Object.entries(tacticData.stix)) { - if (fieldSchemas[key] && hasValue(value) && key != 'x_mitre_shortname') { - console.log(`Key: ${key}, Value: ${value}`); - - // Dynamically validate the field using its schema - const schema = fieldSchemas[key]; - const validationResult = schema.safeParse(value); - - if (!validationResult.success) { - // Format Zod errors into a more readable structure - const errors = validationResult.error.errors.map((err) => ({ - path: err.path.join('.'), - message: err.message, - code: err.code, - })); - - logger.warn( - `Validation failed for field "${key}" in tactic ${tacticData.stix.id}: ${JSON.stringify(errors)}`, - ); - - return res.status(400).json({ - error: 'Validation Error', - message: `Field "${key}" does not meet ATT&CK Data Model requirements`, - details: errors, - }); - } - } + const tacticDataFiltered = filterObject(tacticData.stix); + const validationResult = tacticSchema.partial().safeParse(tacticDataFiltered); + if (!validationResult.success) { + // Format Zod errors into a more readable structure + const errors = validationResult.error.issues.map((err) => ({ + path: err.path.join('.'), + message: err.message, + code: err.code, + })); + + logger.warn( + logger.warn(`Tactic validation failed for ${tacticData.stix.id}: ${JSON.stringify(errors)}`) + ); + + return res.status(400).json({ + error: 'Validation Error', + message: 'Tactic does not meet ATT&CK Data Model requirements', + details: errors, + }); } return next(); } catch (err) { @@ -95,7 +91,7 @@ module.exports = async function validateTacticForSpecCompliance(req, res, next) if (!validationResult.success) { // Format Zod errors into a more readable structure - const errors = validationResult.error.errors.map((err) => ({ + const errors = validationResult.error.issues.map((err) => ({ path: err.path.join('.'), message: err.message, code: err.code, diff --git a/app/routes/validate-routes.js b/app/routes/validate-routes.js new file mode 100644 index 00000000..e04c1518 --- /dev/null +++ b/app/routes/validate-routes.js @@ -0,0 +1,15 @@ +'use strict'; + +const express = require('express'); + +const validateController = require('../controllers/validate-controller'); +const authn = require('../lib/authn-middleware'); +const authz = require('../lib/authz-middleware'); + +const router = express.Router(); + +router + .route('/validate') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), validateController.validate); + +module.exports = router; diff --git a/app/services/validate-service.js b/app/services/validate-service.js new file mode 100644 index 00000000..e47b08af --- /dev/null +++ b/app/services/validate-service.js @@ -0,0 +1,56 @@ +'use strict'; + +const { + techniqueSchema, + campaignSchema, + // Add more schemas as needed +} = require('@mitre-attack/attack-data-model'); + +const logger = require('../lib/logger'); + +class ValidationService { + constructor() { + // Map STIX types to schemas + this.stixSchemas = { + 'attack-pattern': techniqueSchema, + 'campaign': campaignSchema, + }; + } + + /** + * Validate a STIX object against its schema. + * @param {string} type - The STIX type (e.g., 'attack-pattern', 'campaign') + * @param {object} stix - The STIX object to validate + * @returns {object} { errors: Array } + */ + validate(type, stix) { + const schema = this.stixSchemas[type]; + if (!schema) { + logger.warn(`Unknown STIX type: ${type}`); + return { + errors: [{ + code: 'unknown_type', + path: ['type'], + message: `Unknown STIX type: ${type}` + }] + }; + } + + const result = schema.partial().safeParse(stix); + + if (result.success) { + return { errors: [] }; + } else { + // Map Zod errors to your error format + return { + errors: result.error.issues.map(err => ({ + code: err.code, + path: err.path, + message: err.message + })) + }; + } + } +} + +module.exports = ValidationService; diff --git a/package-lock.json b/package-lock.json index 398e3d24..96bcb3cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", + "@mitre-attack/attack-data-model": "^4.0.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", @@ -2009,6 +2010,30 @@ "version": "7.1.3", "license": "MIT" }, + "node_modules/@mitre-attack/attack-data-model": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.0.1.tgz", + "integrity": "sha512-SGSIPKz75LV6Blpf8Le0rapvIjldLSl6UM+TYEVt4vo/n1Bk/bsh/kdBYH4QBQPoNvpM+ftO12YKJwSHWDr96Q==", + "license": "APACHE-2.0", + "dependencies": { + "axios": "^1.9.0", + "uuid": "^10.0.0", + "zod": "^4.0.5" + } + }, + "node_modules/@mitre-attack/attack-data-model/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", "license": "MIT", @@ -3129,6 +3154,17 @@ "version": "0.4.0", "license": "MIT" }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -4520,6 +4556,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "dev": true, @@ -5613,7 +5664,6 @@ "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, "funding": [ { "type": "individual", @@ -5646,11 +5696,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -6107,6 +6161,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -11693,6 +11762,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "license": "MIT", @@ -13770,6 +13845,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz", + "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index b9c868f6..5af09f3d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", + "@mitre-attack/attack-data-model": "^4.0.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", From 798229023c0b2d467e2d2310cccfac8d3055e5de Mon Sep 17 00:00:00 2001 From: adpare Date: Sat, 9 Aug 2025 11:34:44 -0400 Subject: [PATCH 005/370] chore: format code --- app/api/definitions/paths/validate-paths.yml | 84 +++++++++---------- app/controllers/validate-controller.js | 20 +++-- .../tactic-validation-middleware.js | 8 +- app/services/validate-service.js | 20 +++-- 4 files changed, 70 insertions(+), 62 deletions(-) diff --git a/app/api/definitions/paths/validate-paths.yml b/app/api/definitions/paths/validate-paths.yml index c18df7a2..7bd474cd 100644 --- a/app/api/definitions/paths/validate-paths.yml +++ b/app/api/definitions/paths/validate-paths.yml @@ -1,50 +1,50 @@ paths: /api/validate: - post: - summary: 'Validate a STIX object' - operationId: 'validate-stix-object' - description: | - Validates a STIX object against the schema specified in the request body `type` field. - Returns detailed error information as ZodError objects. - tags: - - 'Validation' - requestBody: - required: true + post: + summary: 'Validate a STIX object' + operationId: 'validate-stix-object' + description: | + Validates a STIX object against the schema specified in the request body `type` field. + Returns detailed error information as ZodError objects. + tags: + - 'Validation' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - stix + properties: + type: + type: string + description: Valid STIX type (e.g., "attack-pattern", "intrustion-set", etc.) + stix: + type: object + description: The STIX object to validate + responses: + '200': + description: 'Validation result' content: application/json: schema: type: object - required: - - type - - stix properties: - type: - type: string - description: Valid STIX type (e.g., "attack-pattern", "intrustion-set", etc.) - stix: - type: object - description: The STIX object to validate - responses: - '200': - description: 'Validation result' - content: - application/json: - schema: - type: object - properties: - errors: - type: array - items: - type: object - properties: - code: - type: string - example: custom - path: - type: array - items: - type: string - message: + errors: + type: array + items: + type: object + properties: + code: + type: string + example: custom + path: + type: array + items: type: string - '400': - description: 'Invalid request' + message: + type: string + '400': + description: 'Invalid request' diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js index 100b2b57..8bbcd3ed 100644 --- a/app/controllers/validate-controller.js +++ b/app/controllers/validate-controller.js @@ -9,14 +9,21 @@ exports.validate = async function (req, res) { const { type, stix } = req.body || {}; // Basic request validation - if (typeof type !== 'string' || typeof stix !== 'object' || stix === null || Array.isArray(stix)) { + if ( + typeof type !== 'string' || + typeof stix !== 'object' || + stix === null || + Array.isArray(stix) + ) { logger.warn('Invalid request body for /api/validate'); return res.status(400).json({ - errors: [{ - code: 'invalid_request', - path: [], - message: 'Request body must have a string "type" and an object "stix".' - }] + errors: [ + { + code: 'invalid_request', + path: [], + message: 'Request body must have a string "type" and an object "stix".', + }, + ], }); } @@ -30,7 +37,6 @@ exports.validate = async function (req, res) { logger.debug(`Validation completed for type: ${type}`); return res.status(200).json(result); - } catch (err) { logger.error('Failed to validate STIX object: ' + err); return res.status(500).send('Unable to validate STIX object. Server error.'); diff --git a/app/middleware/tactic-validation-middleware.js b/app/middleware/tactic-validation-middleware.js index 77aa765e..27752659 100644 --- a/app/middleware/tactic-validation-middleware.js +++ b/app/middleware/tactic-validation-middleware.js @@ -19,9 +19,7 @@ function hasValue(field) { } function filterObject(obj) { - return Object.fromEntries( - Object.entries(obj).filter(([key, value]) => hasValue(value)) - ); + Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); } module.exports = async function validateTacticForSpecCompliance(req, res, next) { @@ -47,7 +45,9 @@ module.exports = async function validateTacticForSpecCompliance(req, res, next) })); logger.warn( - logger.warn(`Tactic validation failed for ${tacticData.stix.id}: ${JSON.stringify(errors)}`) + logger.warn( + `Tactic validation failed for ${tacticData.stix.id}: ${JSON.stringify(errors)}`, + ), ); return res.status(400).json({ diff --git a/app/services/validate-service.js b/app/services/validate-service.js index e47b08af..7ab7d5be 100644 --- a/app/services/validate-service.js +++ b/app/services/validate-service.js @@ -13,7 +13,7 @@ class ValidationService { // Map STIX types to schemas this.stixSchemas = { 'attack-pattern': techniqueSchema, - 'campaign': campaignSchema, + campaign: campaignSchema, }; } @@ -28,11 +28,13 @@ class ValidationService { if (!schema) { logger.warn(`Unknown STIX type: ${type}`); return { - errors: [{ - code: 'unknown_type', - path: ['type'], - message: `Unknown STIX type: ${type}` - }] + errors: [ + { + code: 'unknown_type', + path: ['type'], + message: `Unknown STIX type: ${type}`, + }, + ], }; } @@ -43,11 +45,11 @@ class ValidationService { } else { // Map Zod errors to your error format return { - errors: result.error.issues.map(err => ({ + errors: result.error.issues.map((err) => ({ code: err.code, path: err.path, - message: err.message - })) + message: err.message, + })), }; } } From e93e748f8ea1cd3a30d07a7efff8b4787ce4efb0 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 28 Aug 2025 15:15:04 -0400 Subject: [PATCH 006/370] feat: attempt to use campaign middleware --- app/api/definitions/paths/validate-paths.yml | 3 + app/controllers/validate-controller.js | 8 +- .../campaign-validation-middleware.js | 119 ++++++++++++++++++ .../tactic-validation-middleware.js | 2 +- app/services/validate-service.js | 28 ++++- 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 app/middleware/campaign-validation-middleware.js diff --git a/app/api/definitions/paths/validate-paths.yml b/app/api/definitions/paths/validate-paths.yml index 7bd474cd..1bc1dafa 100644 --- a/app/api/definitions/paths/validate-paths.yml +++ b/app/api/definitions/paths/validate-paths.yml @@ -21,6 +21,9 @@ paths: type: type: string description: Valid STIX type (e.g., "attack-pattern", "intrustion-set", etc.) + status: + type: string + description: Workflow status - "work-in-progress", "awaiting-reviewed", "reviewed" stix: type: object description: The STIX object to validate diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js index 8bbcd3ed..b13a0dc7 100644 --- a/app/controllers/validate-controller.js +++ b/app/controllers/validate-controller.js @@ -6,11 +6,12 @@ const validateService = new ValidationService(); exports.validate = async function (req, res) { try { - const { type, stix } = req.body || {}; + const { type, status, stix } = req.body || {}; // Basic request validation if ( typeof type !== 'string' || + typeof status !== 'string' || typeof stix !== 'object' || stix === null || Array.isArray(stix) @@ -21,14 +22,15 @@ exports.validate = async function (req, res) { { code: 'invalid_request', path: [], - message: 'Request body must have a string "type" and an object "stix".', + message: + 'Request body must have a string "type", string "status" and an object "stix".', }, ], }); } // Validate the STIX object using the instance method - const result = validateService.validate(type, stix); + const result = validateService.validate(type, status, stix); if (result.errors.length && result.errors[0].code === 'unknown_type') { logger.warn(`Unknown STIX type: ${type}`); diff --git a/app/middleware/campaign-validation-middleware.js b/app/middleware/campaign-validation-middleware.js new file mode 100644 index 00000000..0cba6867 --- /dev/null +++ b/app/middleware/campaign-validation-middleware.js @@ -0,0 +1,119 @@ +'use strict'; + +const { campaignSchema } = require('@mitre-attack/attack-data-model'); +const logger = require('../lib/logger'); +const campaignsService = require('../services/campaigns-service'); +const { workflowStates } = require('../dtos'); + +/** + * Middleware to validate a campaign object against the ATT&CK Data Model + * when transitioning from 'work-in-progress' to 'awaiting-review' + */ +function hasValue(field) { + return ( + field !== undefined && + field !== null && + field !== '' && + !(Array.isArray(field) && field.length === 0) + ); +} + +function filterObject(obj) { + return Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); +} + +module.exports = async function validateCampaignForSpecCompliance(req, res, next) { + const campaignData = req.body; + + // Skip validation if campaign is not in 'awaiting-review' state + if ( + !campaignData?.workspace?.workflow?.state || + (campaignData.workspace.workflow.state !== workflowStates.WorkflowStates.AWAITING_REVIEW && + campaignData.workspace.workflow.state !== workflowStates.WorkflowStates.REVIEWED) + ) { + try { + // Validate the campaign object using the ATT&CK Data Model + logger.debug(`Validating campaign ${campaignData.stix.id} against ATT&CK Data Model`); + const campaignDataFiltered = filterObject(campaignData.stix); + const validationResult = campaignSchema.partial().safeParse(campaignDataFiltered); + if (!validationResult.success) { + // Format Zod errors into a more readable structure + const errors = validationResult.error.issues.map((err) => ({ + path: err.path.join('.'), + message: err.message, + code: err.code, + })); + + logger.warn( + logger.warn( + `Campaign validation failed for ${campaignData.stix.id}: ${JSON.stringify(errors)}`, + ), + ); + + return res.status(400).json({ + error: 'Validation Error', + message: 'Campaign does not meet ATT&CK Data Model requirements', + details: errors, + }); + } + return next(); + } catch (err) { + logger.error(`Campaign validation error: ${err.message}`); + return res.status(500).send('Unable to validate campaign. Server error.'); + } + } + + try { + // Check if we're updating an existing campaign + const isUpdate = req.params.stixId && req.params.modified; + + if (isUpdate) { + const existingCampaign = await campaignsService.retrieveVersionById( + req.params.stixId, + req.params.modified, + ); + + // Only validate if transitioning from 'work-in-progress' to 'awaiting-review' + if (!workflowStates.isTransitioningToReview(campaignData, existingCampaign)) { + return next(); + } + } + + // Validate the campaign object using the ATT&CK Data Model + logger.debug(`Validating campaign ${campaignData.stix.id} against ATT&CK Data Model`); + + // Use the partial schema to validate only the fields provided in req.body.stix + const validationResult = campaignSchema.safeParse(campaignData.stix); + + // Use the ATT&CK Data Model's campaign schema to validate + // This uses Zod for validation with robust error reporting + // const validationResult = campaignSchema.safeParse(campaignData.stix); + + if (!validationResult.success) { + // Format Zod errors into a more readable structure + const errors = validationResult.error.issues.map((err) => ({ + path: err.path.join('.'), + message: err.message, + code: err.code, + })); + + logger.warn( + `Campaign validation failed for ${campaignData.stix.id}: ${JSON.stringify(errors)}`, + ); + + return res.status(400).json({ + error: 'Validation Error', + message: 'Campaign does not meet ATT&CK Data Model requirements', + details: errors, + }); + } + + logger.debug( + `Campaign ${campaignData.stix.id} successfully validated against ATT&CK Data Model schema`, + ); + return next(); + } catch (err) { + logger.error(`Campaign validation error: ${err.message}`); + return res.status(500).send('Unable to validate campaign. Server error.'); + } +}; diff --git a/app/middleware/tactic-validation-middleware.js b/app/middleware/tactic-validation-middleware.js index 27752659..42994f0c 100644 --- a/app/middleware/tactic-validation-middleware.js +++ b/app/middleware/tactic-validation-middleware.js @@ -19,7 +19,7 @@ function hasValue(field) { } function filterObject(obj) { - Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); + return Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); } module.exports = async function validateTacticForSpecCompliance(req, res, next) { diff --git a/app/services/validate-service.js b/app/services/validate-service.js index 7ab7d5be..68b35664 100644 --- a/app/services/validate-service.js +++ b/app/services/validate-service.js @@ -8,6 +8,18 @@ const { const logger = require('../lib/logger'); +// function hasValue(field) { +// return ( +// field !== undefined && +// field !== null && +// field !== '' && +// !(Array.isArray(field) && field.length === 0) +// ); +// } + +// function filterObject(obj) { +// return Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); +// } class ValidationService { constructor() { // Map STIX types to schemas @@ -20,10 +32,11 @@ class ValidationService { /** * Validate a STIX object against its schema. * @param {string} type - The STIX type (e.g., 'attack-pattern', 'campaign') + * @param {object} status - The workflow status of the object "work-in-progress" || "awaiting-reviewed" || "reviewed" * @param {object} stix - The STIX object to validate * @returns {object} { errors: Array } */ - validate(type, stix) { + validate(type, status, stix) { const schema = this.stixSchemas[type]; if (!schema) { logger.warn(`Unknown STIX type: ${type}`); @@ -38,7 +51,18 @@ class ValidationService { }; } - const result = schema.partial().safeParse(stix); + const omit_dict = { x_mitre_modified_by_ref: true, x_mitre_attack_spec_version: true }; + if (stix.type == 'campaign' || stix.type == 'intrusion-set') { + omit_dict.x_mitre_domains = true; + } + + let result; + + if (status == 'work-in-progress') { + result = schema.partial().safeParse(stix); + } else { + result = schema.omit(omit_dict).safeParse(stix); + } if (result.success) { return { errors: [] }; From dcc8dfc7653be7ab46e9ff6153d579dced905e03 Mon Sep 17 00:00:00 2001 From: adpare Date: Tue, 2 Sep 2025 01:40:45 -0400 Subject: [PATCH 007/370] feat: add validation for tactics and techniques --- app/api/definitions/components/tactics.yml | 4 +- app/api/definitions/components/techniques.yml | 4 +- app/services/validate-service.js | 56 ++++++++++++++----- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/app/api/definitions/components/tactics.yml b/app/api/definitions/components/tactics.yml index c28a5fd2..590fff33 100644 --- a/app/api/definitions/components/tactics.yml +++ b/app/api/definitions/components/tactics.yml @@ -9,9 +9,9 @@ components: workspace: $ref: 'workspace.yml#/components/schemas/workspace' stix: - $ref: '#/components/schemas/x-mitre-tactic' + $ref: '#/components/schemas/x-mitre-tactic-stix-object' - x-mitre-tactic: + x-mitre-tactic-stix-object: allOf: - $ref: 'stix-common.yml#/components/schemas/stix-common' - type: object diff --git a/app/api/definitions/components/techniques.yml b/app/api/definitions/components/techniques.yml index 34e92122..a93e01f0 100644 --- a/app/api/definitions/components/techniques.yml +++ b/app/api/definitions/components/techniques.yml @@ -9,9 +9,9 @@ components: workspace: $ref: 'workspace.yml#/components/schemas/workspace' stix: - $ref: '#/components/schemas/stix-attack-pattern' + $ref: '#/components/schemas/attack-pattern-stix-object' - stix-attack-pattern: + attack-pattern-stix-object: allOf: - $ref: 'stix-common.yml#/components/schemas/stix-common' - type: object diff --git a/app/services/validate-service.js b/app/services/validate-service.js index 68b35664..72d97b27 100644 --- a/app/services/validate-service.js +++ b/app/services/validate-service.js @@ -1,31 +1,52 @@ 'use strict'; +const yaml = require('js-yaml'); +const fs = require('fs'); const { techniqueSchema, campaignSchema, + tacticSchema, // Add more schemas as needed } = require('@mitre-attack/attack-data-model'); const logger = require('../lib/logger'); -// function hasValue(field) { -// return ( -// field !== undefined && -// field !== null && -// field !== '' && -// !(Array.isArray(field) && field.length === 0) -// ); -// } +function listToDict(list) { + return Object.fromEntries(list.map(prop => [prop, true])); +} + +// Utility: Extract required fields for a schema from YAML (handles allOf) +function getRequiredFields(doc, schemaName) { + const schema = doc.components.schemas[schemaName]; + if (!schema) throw new Error(`Schema "${schemaName}" not found`); + let required = []; -// function filterObject(obj) { -// return Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); -// } + if (schema.required) { + required = schema.required; + } + // Handle allOf + if (schema.allOf) { + for (const item of schema.allOf) { + if (item.required) { + required = required.concat(item.required); + } + } + } + return required; +} class ValidationService { constructor() { // Map STIX types to schemas this.stixSchemas = { 'attack-pattern': techniqueSchema, - campaign: campaignSchema, + 'campaign': campaignSchema, + 'x-mitre-tactic': tacticSchema + }; + + this.stixToAttack = { + 'attack-pattern': 'techniques', + 'campaign': 'campaigns', + 'x-mitre-tactic': 'tactics', }; } @@ -37,6 +58,15 @@ class ValidationService { * @returns {object} { errors: Array } */ validate(type, status, stix) { + const attackType = this.stixToAttack[type]; + if (!attackType) { + return { errors: [`Unknown STIX type: ${type}`] }; + } + const file = fs.readFileSync(`app/api/definitions/components/${attackType}.yml`, 'utf8'); + const doc = yaml.load(file); + const req = getRequiredFields(doc,stix.type + '-stix-object'); + const props = req; + const dict = listToDict(props); const schema = this.stixSchemas[type]; if (!schema) { logger.warn(`Unknown STIX type: ${type}`); @@ -59,7 +89,7 @@ class ValidationService { let result; if (status == 'work-in-progress') { - result = schema.partial().safeParse(stix); + result = schema.partial().required(dict).safeParse(stix); } else { result = schema.omit(omit_dict).safeParse(stix); } From c60561759213a6b3544a32ee0cc6a4d4f063b068 Mon Sep 17 00:00:00 2001 From: adpare Date: Tue, 2 Sep 2025 01:41:37 -0400 Subject: [PATCH 008/370] feat: add validation for tactics and techniques --- app/services/validate-service.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/services/validate-service.js b/app/services/validate-service.js index 72d97b27..3cb2970f 100644 --- a/app/services/validate-service.js +++ b/app/services/validate-service.js @@ -12,7 +12,7 @@ const { const logger = require('../lib/logger'); function listToDict(list) { - return Object.fromEntries(list.map(prop => [prop, true])); + return Object.fromEntries(list.map((prop) => [prop, true])); } // Utility: Extract required fields for a schema from YAML (handles allOf) @@ -39,13 +39,13 @@ class ValidationService { // Map STIX types to schemas this.stixSchemas = { 'attack-pattern': techniqueSchema, - 'campaign': campaignSchema, - 'x-mitre-tactic': tacticSchema + campaign: campaignSchema, + 'x-mitre-tactic': tacticSchema, }; this.stixToAttack = { 'attack-pattern': 'techniques', - 'campaign': 'campaigns', + campaign: 'campaigns', 'x-mitre-tactic': 'tactics', }; } @@ -64,7 +64,7 @@ class ValidationService { } const file = fs.readFileSync(`app/api/definitions/components/${attackType}.yml`, 'utf8'); const doc = yaml.load(file); - const req = getRequiredFields(doc,stix.type + '-stix-object'); + const req = getRequiredFields(doc, stix.type + '-stix-object'); const props = req; const dict = listToDict(props); const schema = this.stixSchemas[type]; From e7067e0f28f2b06effd183bf003e3cff01ec8003 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:59:04 -0400 Subject: [PATCH 009/370] refactor: replace openapi request body validator in yaml spec files - change all stix component pointers to simple object check - validation will be replaced with custom ADM middleware --- app/api/definitions/paths/analytics-paths.yml | 6 +- app/api/definitions/paths/assets-paths.yml | 62 ++++++++++--------- app/api/definitions/paths/campaigns-paths.yml | 6 +- .../definitions/paths/collections-paths.yml | 3 +- .../paths/data-components-paths.yml | 6 +- .../definitions/paths/data-sources-paths.yml | 6 +- .../paths/detection-strategies-paths.yml | 6 +- app/api/definitions/paths/groups-paths.yml | 6 +- .../definitions/paths/identities-paths.yml | 6 +- app/api/definitions/paths/matrices-paths.yml | 6 +- .../definitions/paths/mitigations-paths.yml | 6 +- .../definitions/paths/relationships-paths.yml | 6 +- app/api/definitions/paths/tactics-paths.yml | 6 +- .../definitions/paths/techniques-paths.yml | 6 +- 14 files changed, 82 insertions(+), 55 deletions(-) diff --git a/app/api/definitions/paths/analytics-paths.yml b/app/api/definitions/paths/analytics-paths.yml index 7b5b21cd..62d6d0b6 100644 --- a/app/api/definitions/paths/analytics-paths.yml +++ b/app/api/definitions/paths/analytics-paths.yml @@ -116,7 +116,8 @@ paths: content: application/json: schema: - $ref: '../components/analytics.yml#/components/schemas/analytic' + type: object + # $ref: '../components/analytics.yml#/components/schemas/analytic' # TODO delete after ADM integration complete responses: '201': description: 'The analytic has been successfully created.' @@ -242,7 +243,8 @@ paths: content: application/json: schema: - $ref: '../components/analytics.yml#/components/schemas/analytic' + type: object + # $ref: '../components/analytics.yml#/components/schemas/analytic' # TODO delete after ADM integration complete responses: '200': description: 'The analytic was updated.' diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index 06744b9a..a27cdc29 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -128,7 +128,8 @@ paths: content: application/json: schema: - $ref: '../components/assets.yml#/components/schemas/asset' + type: object + # $ref: '../components/assets.yml#/components/schemas/asset' responses: '201': description: 'The asset has been successfully created.' @@ -252,7 +253,8 @@ paths: content: application/json: schema: - $ref: '../components/assets.yml#/components/schemas/asset' + type: object + # $ref: '../components/assets.yml#/components/schemas/asset' # TODO delete after ADM integration complete responses: '200': description: 'The asset was updated.' @@ -291,31 +293,31 @@ paths: '404': description: 'An asset with the requested STIX id and modified date was not found.' - /api/assets/validate: - post: - summary: 'Validate an asset or a field of an asset' - operationId: 'asset-validate' - description: | - This endpoint validates an asset object or a field of an asset without creating or updating it. - tags: - - 'Assets' - requestBody: - required: true - content: - application/json: - schema: - $ref: '../components/assets.yml#/components/schemas/asset' - responses: - '200': - description: 'Validation result.' - content: - application/json: - schema: - type: object - properties: - valid: - type: boolean - errors: - type: array - items: - type: string + # /api/assets/validate: + # post: + # summary: 'Validate an asset or a field of an asset' + # operationId: 'asset-validate' + # description: | + # This endpoint validates an asset object or a field of an asset without creating or updating it. + # tags: + # - 'Assets' + # requestBody: + # required: true + # content: + # application/json: + # schema: + # $ref: '../components/assets.yml#/components/schemas/asset' + # responses: + # '200': + # description: 'Validation result.' + # content: + # application/json: + # schema: + # type: object + # properties: + # valid: + # type: boolean + # errors: + # type: array + # items: + # type: string diff --git a/app/api/definitions/paths/campaigns-paths.yml b/app/api/definitions/paths/campaigns-paths.yml index c1dca1e4..eb3f230f 100644 --- a/app/api/definitions/paths/campaigns-paths.yml +++ b/app/api/definitions/paths/campaigns-paths.yml @@ -104,7 +104,8 @@ paths: content: application/json: schema: - $ref: '../components/campaigns.yml#/components/schemas/campaign' + type: object + # $ref: '../components/campaigns.yml#/components/schemas/campaign' # TODO delete after ADM integration complete responses: '201': description: 'The campaign has been successfully created.' @@ -230,7 +231,8 @@ paths: content: application/json: schema: - $ref: '../components/campaigns.yml#/components/schemas/campaign' + type: object + # $ref: '../components/campaigns.yml#/components/schemas/campaign' # TODO delete after ADM integration complete responses: '200': description: 'The campaign was updated.' diff --git a/app/api/definitions/paths/collections-paths.yml b/app/api/definitions/paths/collections-paths.yml index 6b67e0cf..d69602d7 100644 --- a/app/api/definitions/paths/collections-paths.yml +++ b/app/api/definitions/paths/collections-paths.yml @@ -107,7 +107,8 @@ paths: content: application/json: schema: - $ref: '../components/collections.yml#/components/schemas/collection' + type: object + # $ref: '../components/collections.yml#/components/schemas/collection' # TODO delete after ADM integration complete responses: '201': description: 'The collection has been successfully created.' diff --git a/app/api/definitions/paths/data-components-paths.yml b/app/api/definitions/paths/data-components-paths.yml index 1a9f662d..94120881 100644 --- a/app/api/definitions/paths/data-components-paths.yml +++ b/app/api/definitions/paths/data-components-paths.yml @@ -104,7 +104,8 @@ paths: content: application/json: schema: - $ref: '../components/data-components.yml#/components/schemas/data-component' + type: object + # $ref: '../components/data-components.yml#/components/schemas/data-component' # TODO delete after ADM integration complete responses: '201': description: 'The data component has been successfully created.' @@ -230,7 +231,8 @@ paths: content: application/json: schema: - $ref: '../components/data-components.yml#/components/schemas/data-component' + type: object + # $ref: '../components/data-components.yml#/components/schemas/data-component' # TODO delete after ADM integration complete responses: '200': description: 'The data component was updated.' diff --git a/app/api/definitions/paths/data-sources-paths.yml b/app/api/definitions/paths/data-sources-paths.yml index 1d9b5133..3cc2cf84 100644 --- a/app/api/definitions/paths/data-sources-paths.yml +++ b/app/api/definitions/paths/data-sources-paths.yml @@ -128,7 +128,8 @@ paths: content: application/json: schema: - $ref: '../components/data-sources.yml#/components/schemas/data-source' + type: object + # $ref: '../components/data-sources.yml#/components/schemas/data-source' # TODO delete after ADM integration complete responses: '201': description: 'The data source has been successfully created.' @@ -268,7 +269,8 @@ paths: content: application/json: schema: - $ref: '../components/data-sources.yml#/components/schemas/data-source' + type: object + # $ref: '../components/data-sources.yml#/components/schemas/data-source' # TODO delete after ADM integration complete responses: '200': description: 'The data source was updated.' diff --git a/app/api/definitions/paths/detection-strategies-paths.yml b/app/api/definitions/paths/detection-strategies-paths.yml index bfc81004..00466053 100644 --- a/app/api/definitions/paths/detection-strategies-paths.yml +++ b/app/api/definitions/paths/detection-strategies-paths.yml @@ -116,7 +116,8 @@ paths: content: application/json: schema: - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' + type: object + # $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' # TODO delete after ADM integration complete responses: '201': description: 'The detection strategy has been successfully created.' @@ -242,7 +243,8 @@ paths: content: application/json: schema: - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' + type: object + # $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' # TODO delete after ADM integration complete responses: '200': description: 'The detection strategy was updated.' diff --git a/app/api/definitions/paths/groups-paths.yml b/app/api/definitions/paths/groups-paths.yml index b42c007b..3862bc5f 100644 --- a/app/api/definitions/paths/groups-paths.yml +++ b/app/api/definitions/paths/groups-paths.yml @@ -104,7 +104,8 @@ paths: content: application/json: schema: - $ref: '../components/groups.yml#/components/schemas/group' + type: object + # $ref: '../components/groups.yml#/components/schemas/group' # TODO delete after ADM integration complete responses: '201': description: 'The group has been successfully created.' @@ -230,7 +231,8 @@ paths: content: application/json: schema: - $ref: '../components/groups.yml#/components/schemas/group' + type: object + # $ref: '../components/groups.yml#/components/schemas/group' # TODO delete after ADM integration complete responses: '200': description: 'The group was updated.' diff --git a/app/api/definitions/paths/identities-paths.yml b/app/api/definitions/paths/identities-paths.yml index 3e14729e..dc0d8781 100644 --- a/app/api/definitions/paths/identities-paths.yml +++ b/app/api/definitions/paths/identities-paths.yml @@ -85,7 +85,8 @@ paths: content: application/json: schema: - $ref: '../components/identities.yml#/components/schemas/identity' + type: object + # $ref: '../components/identities.yml#/components/schemas/identity' # TODO delete after ADM integration complete responses: '201': description: 'The identity has been successfully created.' @@ -209,7 +210,8 @@ paths: content: application/json: schema: - $ref: '../components/identities.yml#/components/schemas/identity' + type: object + # $ref: '../components/identities.yml#/components/schemas/identity' # TODO delete after ADM integration complete responses: '200': description: 'The identity was updated.' diff --git a/app/api/definitions/paths/matrices-paths.yml b/app/api/definitions/paths/matrices-paths.yml index 237b3219..af37c583 100644 --- a/app/api/definitions/paths/matrices-paths.yml +++ b/app/api/definitions/paths/matrices-paths.yml @@ -104,7 +104,8 @@ paths: content: application/json: schema: - $ref: '../components/matrices.yml#/components/schemas/matrix' + type: object + # $ref: '../components/matrices.yml#/components/schemas/matrix' # TODO delete after ADM integration complete responses: '201': description: 'The matrix has been successfully created.' @@ -230,7 +231,8 @@ paths: content: application/json: schema: - $ref: '../components/matrices.yml#/components/schemas/matrix' + type: object + # $ref: '../components/matrices.yml#/components/schemas/matrix' # TODO delete after ADM integration complete responses: '200': description: 'The matrix was updated.' diff --git a/app/api/definitions/paths/mitigations-paths.yml b/app/api/definitions/paths/mitigations-paths.yml index de7e83c1..632dc1a9 100644 --- a/app/api/definitions/paths/mitigations-paths.yml +++ b/app/api/definitions/paths/mitigations-paths.yml @@ -116,7 +116,8 @@ paths: content: application/json: schema: - $ref: '../components/mitigations.yml#/components/schemas/mitigation' + type: object + # $ref: '../components/mitigations.yml#/components/schemas/mitigation' # TODO delete after ADM integration complete responses: '201': description: 'The mitigation has been successfully created.' @@ -242,7 +243,8 @@ paths: content: application/json: schema: - $ref: '../components/mitigations.yml#/components/schemas/mitigation' + type: object + # $ref: '../components/mitigations.yml#/components/schemas/mitigation' # TODO delete after ADM integration complete responses: '200': description: 'The mitigation was updated.' diff --git a/app/api/definitions/paths/relationships-paths.yml b/app/api/definitions/paths/relationships-paths.yml index ff21bc50..84fda6d8 100644 --- a/app/api/definitions/paths/relationships-paths.yml +++ b/app/api/definitions/paths/relationships-paths.yml @@ -175,7 +175,8 @@ paths: content: application/json: schema: - $ref: '../components/relationships.yml#/components/schemas/relationship' + # type: object + $ref: '../components/relationships.yml#/components/schemas/relationship' # TODO delete after ADM integration complete responses: '201': description: 'The relationship has been successfully created.' @@ -301,7 +302,8 @@ paths: content: application/json: schema: - $ref: '../components/relationships.yml#/components/schemas/relationship' + # type: object + $ref: '../components/relationships.yml#/components/schemas/relationship' # TODO delete after ADM integration complete responses: '200': description: 'The relationship was updated.' diff --git a/app/api/definitions/paths/tactics-paths.yml b/app/api/definitions/paths/tactics-paths.yml index 8ada2627..59dd8985 100644 --- a/app/api/definitions/paths/tactics-paths.yml +++ b/app/api/definitions/paths/tactics-paths.yml @@ -116,7 +116,8 @@ paths: content: application/json: schema: - $ref: '../components/tactics.yml#/components/schemas/tactic' + type: object + # $ref: '../components/tactics.yml#/components/schemas/tactic' # TODO delete after ADM integration complete responses: '201': description: 'The tactic has been successfully created.' @@ -242,7 +243,8 @@ paths: content: application/json: schema: - $ref: '../components/tactics.yml#/components/schemas/tactic' + type: object + # $ref: '../components/tactics.yml#/components/schemas/tactic' # TODO delete after ADM integration complete responses: '200': description: 'The tactic was updated.' diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index effaf3fb..82466eaa 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -129,7 +129,8 @@ paths: content: application/json: schema: - $ref: '../components/techniques.yml#/components/schemas/technique' + type: object + # $ref: '../components/techniques.yml#/components/schemas/technique' responses: '201': description: 'The technique has been successfully created.' @@ -255,7 +256,8 @@ paths: content: application/json: schema: - $ref: '../components/techniques.yml#/components/schemas/technique' + type: object + # $ref: '../components/techniques.yml#/components/schemas/technique' # TODO delete after ADM integration complete responses: '200': description: 'The technique was updated.' From c6794616cf30703d40585d8d972ee58debd9dd53 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:06:35 -0400 Subject: [PATCH 010/370] refactor: update validation middleware --- app/lib/validation-middleware.js | 245 ++++++++++++++++++ .../campaign-validation-middleware.js | 119 --------- app/middleware/field-schemas.js | 38 --- .../tactic-validation-middleware.js | 117 --------- 4 files changed, 245 insertions(+), 274 deletions(-) create mode 100644 app/lib/validation-middleware.js delete mode 100644 app/middleware/campaign-validation-middleware.js delete mode 100644 app/middleware/field-schemas.js delete mode 100644 app/middleware/tactic-validation-middleware.js diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js new file mode 100644 index 00000000..c0aa07be --- /dev/null +++ b/app/lib/validation-middleware.js @@ -0,0 +1,245 @@ +'use strict'; + +const { z, ZodError } = require('zod'); +const { StatusCodes } = require('http-status-codes'); +const logger = require('../lib/logger'); +const { processValidationIssues } = require('../services/validate-service'); +const { + createAttackIdSchema, + stixTypeToAttackIdMapping, +} = require('@mitre-attack/attack-data-model/dist/schemas/common/attack-id'); + +/** + * Basic workspace schema (without rigid attack ID validation) + * @type {z.ZodObject} + */ +const workspaceSchema = z.object({ + workflow: z + .object({ + state: z.enum(['work-in-progress', 'awaiting-review', 'reviewed', 'static']), + }) + .optional(), + attackId: z.string().optional(), + collections: z + .array( + z.object({ + collection_ref: z.string(), + collection_modified: z.iso.datetime(), + }), + ) + .optional(), +}); + +/** + * Creates a workspace schema with dynamic attackId validation based on STIX type + * @param {string} stixType - The STIX type (e.g., 'x-mitre-tactic') + * @returns {z.ZodObject} Workspace schema with appropriate attackId validation + */ +function createWorkspaceSchema(stixType) { + logger.debug('Creating workspace schema for STIX type:', { stixType }); + + // Check if this STIX type has an associated attack ID pattern + const hasAttackId = stixType in stixTypeToAttackIdMapping; + logger.debug('STIX type attack ID support:', { stixType, hasAttackId }); + + // Add attackId validation only if this STIX type supports attack IDs + if (hasAttackId) { + logger.debug('Adding dynamic attackId validation for STIX type:', { stixType }); + return workspaceSchema.extend({ + attackId: createAttackIdSchema(stixType).optional(), + }); + } + + // For STIX types without attack IDs, use the basic schema + logger.debug('Using basic workspace schema (no attackId validation) for STIX type:', { + stixType, + }); + return workspaceSchema; +} + +/** + * Factory function that creates a combined workspace+STIX schema with conditional partial validation + * @param {z.ZodObject} stixSchema - The STIX object schema to validate against + * @param {string} workflowState - The workflow state to determine validation strictness + * @param {string[]} omitStixFields - Array of STIX field names to omit from validation + * @returns {z.ZodObject} Combined schema with workspace and conditional stix validation + */ +/** + * Factory function that creates a combined workspace+STIX schema with conditional partial validation + * @param {z.ZodObject} stixSchema - The STIX object schema to validate against + * @param {string} workflowState - The workflow state to determine validation strictness + * @param {string[]} omitStixFields - Array of STIX field names to omit from validation + * @returns {z.ZodObject} Combined schema with workspace and conditional stix validation + */ +function createWorkspaceStixSchema( + stixSchema, + workflowState, + omitStixFields = ['x_mitre_attack_spec_version'], +) { + logger.debug('Creating combined workspace+STIX schema:', { workflowState, omitStixFields }); + + try { + // Extract the STIX type from the schema with fallback for transformed schemas + let stixTypeStringLiteral; + + // Method 1: Direct shape access (works for most schemas) + if (stixSchema.shape?.type?.def?.values?.[0]) { + stixTypeStringLiteral = stixSchema.shape.type.def.values[0]; + } + // Method 2: Through _zod.def.in.def (works for relationshipSchema with .transform()) + else if (stixSchema._zod?.def?.in?.def?.shape?.type?.def?.values?.[0]) { + stixTypeStringLiteral = stixSchema._zod.def.in.def.shape.type.def.values[0]; + } else { + throw new Error('Could not extract STIX type from schema'); + } + + logger.debug('Extracted STIX type from schema:', { stixTypeStringLiteral }); + + // Apply partial validation for work-in-progress, full validation otherwise + const usePartialValidation = workflowState === 'work-in-progress'; + logger.debug('Validation mode:', { workflowState, usePartialValidation }); + + let stixValidationSchema = usePartialValidation ? stixSchema.partial() : stixSchema; + + // Build omit object from array of field names + if (omitStixFields.length > 0) { + const omitObject = omitStixFields.reduce((acc, field) => { + acc[field] = true; + return acc; + }, {}); + stixValidationSchema = stixValidationSchema.omit(omitObject); + } + + const combinedSchema = z.object({ + workspace: createWorkspaceSchema(stixTypeStringLiteral), + stix: stixValidationSchema, + }); + + logger.debug('Successfully created combined schema'); + return combinedSchema; + } catch (error) { + logger.warn('Could not extract STIX type from schema, using basic validation:', { + error: error.message, + workflowState, + omitStixFields, + }); + + let stixValidationSchema = + workflowState === 'work-in-progress' ? stixSchema.partial() : stixSchema; + + // Apply omit in error case as well + if (omitStixFields.length > 0) { + const omitObject = omitStixFields.reduce((acc, field) => { + acc[field] = true; + return acc; + }, {}); + stixValidationSchema = stixValidationSchema.omit(omitObject); + } + + return z.object({ + workspace: workspaceSchema, + stix: stixValidationSchema, + }); + } +} + +/** + * Middleware for parsing the request body using a specified STIX schema from the ATT&CK Data Model. + * Both the `workspace` and `stix` keys are checked. + * @param {*} stixSchema + * @returns + */ +function validateWorkspaceStixData(stixSchema) { + return (req, res, next) => { + logger.debug('Starting workspace+STIX validation middleware'); + logger.debug('Request body structure:', { + hasWorkspace: !!req.body?.workspace, + hasStix: !!req.body?.stix, + bodyKeys: Object.keys(req.body || {}), + workflowState: req.body?.workspace?.workflow?.state, + }); + + try { + // Extract workflow state from request body + const workflowState = req.body?.workspace?.workflow?.state || 'reviewed'; // Default to strict validation + logger.debug('Determined workflow state:', { + workflowState, + isDefault: !req.body?.workspace?.workflow?.state, + }); + + // Create schema with conditional validation based on workflow state + const combinedSchema = createWorkspaceStixSchema(stixSchema, workflowState); + + logger.debug('Attempting to parse request body with combined schema'); + combinedSchema.parse(req.body); + + logger.debug('Validation successful, proceeding to next middleware'); + next(); + } catch (error) { + logger.debug('Validation failed:', { + errorType: error.constructor.name, + isZodError: error instanceof ZodError, + }); + + if (error instanceof ZodError) { + // Extract STIX type from request body for error-to-warning conversion + const stixType = req.body?.stix?.type; + + // Process validation issues using shared logic to separate errors from warnings + const { errors, warnings } = processValidationIssues(error.issues, stixType); + + logger.debug('Processed validation issues:', { + issueCount: error.issues?.length, + errorCount: errors.length, + warningCount: warnings.length, + errors, + warnings, + }); + + // Only block the request if there are actual errors (warnings are OK) + if (errors.length > 0) { + logger.info('Request validation failed', { + endpoint: req.path, + method: req.method, + validationErrors: errors, + validationWarnings: warnings, + }); + + res.status(StatusCodes.BAD_REQUEST).json({ + error: 'Invalid data', + details: errors, + warnings: warnings.length > 0 ? warnings : undefined, + }); + } else { + // Only warnings, allow the request to proceed + logger.info('Request validation passed with warnings', { + endpoint: req.path, + method: req.method, + validationWarnings: warnings, + }); + + // Attach warnings to request for potential use by controllers + req.validationWarnings = warnings; + next(); + } + } else { + logger.error('Validation middleware error:', { + error: error.message, + stack: error.stack, + endpoint: req.path, + method: req.method, + }); + res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' }); + } + } + }; +} + +module.exports = { + /** Express middleware factory for workspace+STIX validation */ + validateWorkspaceStixData, + /** Factory function for creating combined workspace+STIX schemas */ + createWorkspaceStixSchema, + /** Basic workspace schema without dynamic attackId validation */ + workspaceSchema, +}; diff --git a/app/middleware/campaign-validation-middleware.js b/app/middleware/campaign-validation-middleware.js deleted file mode 100644 index 0cba6867..00000000 --- a/app/middleware/campaign-validation-middleware.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -const { campaignSchema } = require('@mitre-attack/attack-data-model'); -const logger = require('../lib/logger'); -const campaignsService = require('../services/campaigns-service'); -const { workflowStates } = require('../dtos'); - -/** - * Middleware to validate a campaign object against the ATT&CK Data Model - * when transitioning from 'work-in-progress' to 'awaiting-review' - */ -function hasValue(field) { - return ( - field !== undefined && - field !== null && - field !== '' && - !(Array.isArray(field) && field.length === 0) - ); -} - -function filterObject(obj) { - return Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); -} - -module.exports = async function validateCampaignForSpecCompliance(req, res, next) { - const campaignData = req.body; - - // Skip validation if campaign is not in 'awaiting-review' state - if ( - !campaignData?.workspace?.workflow?.state || - (campaignData.workspace.workflow.state !== workflowStates.WorkflowStates.AWAITING_REVIEW && - campaignData.workspace.workflow.state !== workflowStates.WorkflowStates.REVIEWED) - ) { - try { - // Validate the campaign object using the ATT&CK Data Model - logger.debug(`Validating campaign ${campaignData.stix.id} against ATT&CK Data Model`); - const campaignDataFiltered = filterObject(campaignData.stix); - const validationResult = campaignSchema.partial().safeParse(campaignDataFiltered); - if (!validationResult.success) { - // Format Zod errors into a more readable structure - const errors = validationResult.error.issues.map((err) => ({ - path: err.path.join('.'), - message: err.message, - code: err.code, - })); - - logger.warn( - logger.warn( - `Campaign validation failed for ${campaignData.stix.id}: ${JSON.stringify(errors)}`, - ), - ); - - return res.status(400).json({ - error: 'Validation Error', - message: 'Campaign does not meet ATT&CK Data Model requirements', - details: errors, - }); - } - return next(); - } catch (err) { - logger.error(`Campaign validation error: ${err.message}`); - return res.status(500).send('Unable to validate campaign. Server error.'); - } - } - - try { - // Check if we're updating an existing campaign - const isUpdate = req.params.stixId && req.params.modified; - - if (isUpdate) { - const existingCampaign = await campaignsService.retrieveVersionById( - req.params.stixId, - req.params.modified, - ); - - // Only validate if transitioning from 'work-in-progress' to 'awaiting-review' - if (!workflowStates.isTransitioningToReview(campaignData, existingCampaign)) { - return next(); - } - } - - // Validate the campaign object using the ATT&CK Data Model - logger.debug(`Validating campaign ${campaignData.stix.id} against ATT&CK Data Model`); - - // Use the partial schema to validate only the fields provided in req.body.stix - const validationResult = campaignSchema.safeParse(campaignData.stix); - - // Use the ATT&CK Data Model's campaign schema to validate - // This uses Zod for validation with robust error reporting - // const validationResult = campaignSchema.safeParse(campaignData.stix); - - if (!validationResult.success) { - // Format Zod errors into a more readable structure - const errors = validationResult.error.issues.map((err) => ({ - path: err.path.join('.'), - message: err.message, - code: err.code, - })); - - logger.warn( - `Campaign validation failed for ${campaignData.stix.id}: ${JSON.stringify(errors)}`, - ); - - return res.status(400).json({ - error: 'Validation Error', - message: 'Campaign does not meet ATT&CK Data Model requirements', - details: errors, - }); - } - - logger.debug( - `Campaign ${campaignData.stix.id} successfully validated against ATT&CK Data Model schema`, - ); - return next(); - } catch (err) { - logger.error(`Campaign validation error: ${err.message}`); - return res.status(500).send('Unable to validate campaign. Server error.'); - } -}; diff --git a/app/middleware/field-schemas.js b/app/middleware/field-schemas.js deleted file mode 100644 index 20f8a2fe..00000000 --- a/app/middleware/field-schemas.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const { - nameSchema, - tacticIdSchema, - descriptionSchema, - externalReferencesSchema, - objectMarkingRefsSchema, - xMitreDomainsSchema, - xMitreShortNameSchema, - xMitreModifiedByRefSchema, - stixTypeSchema, - xMitreContributorsSchema, - xMitreAttackSpecVersionSchema, - xMitreVersionSchema, - xMitreOldAttackIdSchema, - xMitreDeprecatedSchema, -} = require('@mitre-attack/attack-data-model'); - -const fieldSchemas = { - name: nameSchema, - id: tacticIdSchema, - type: stixTypeSchema, - x_mitre_version: xMitreVersionSchema, - description: descriptionSchema, - created_by_ref: descriptionSchema, - external_references: externalReferencesSchema, - object_marking_refs: objectMarkingRefsSchema, - x_mitre_domains: xMitreDomainsSchema, - x_mitre_attack_spec_version: xMitreAttackSpecVersionSchema, - x_mitre_shortname: xMitreShortNameSchema, - x_mitre_modified_by_ref: xMitreModifiedByRefSchema, - x_mitre_contributors: xMitreContributorsSchema, - x_mitre_old_attack_id: xMitreOldAttackIdSchema, - x_mitre_deprecated: xMitreDeprecatedSchema, -}; - -module.exports = fieldSchemas; diff --git a/app/middleware/tactic-validation-middleware.js b/app/middleware/tactic-validation-middleware.js deleted file mode 100644 index 42994f0c..00000000 --- a/app/middleware/tactic-validation-middleware.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; - -const { tacticSchema } = require('@mitre-attack/attack-data-model'); -const logger = require('../lib/logger'); -const tacticsService = require('../services/tactics-service'); -const { workflowStates } = require('../dtos'); - -/** - * Middleware to validate a tactic object against the ATT&CK Data Model - * when transitioning from 'work-in-progress' to 'awaiting-review' - */ -function hasValue(field) { - return ( - field !== undefined && - field !== null && - field !== '' && - !(Array.isArray(field) && field.length === 0) - ); -} - -function filterObject(obj) { - return Object.fromEntries(Object.entries(obj).filter((entry) => hasValue(entry[1]))); -} - -module.exports = async function validateTacticForSpecCompliance(req, res, next) { - const tacticData = req.body; - - // Skip validation if tactic is not in 'awaiting-review' state - if ( - !tacticData?.workspace?.workflow?.state || - (tacticData.workspace.workflow.state !== workflowStates.WorkflowStates.AWAITING_REVIEW && - tacticData.workspace.workflow.state !== workflowStates.WorkflowStates.REVIEWED) - ) { - try { - // Validate the tactic object using the ATT&CK Data Model - logger.debug(`Validating tactic ${tacticData.stix.id} against ATT&CK Data Model`); - const tacticDataFiltered = filterObject(tacticData.stix); - const validationResult = tacticSchema.partial().safeParse(tacticDataFiltered); - if (!validationResult.success) { - // Format Zod errors into a more readable structure - const errors = validationResult.error.issues.map((err) => ({ - path: err.path.join('.'), - message: err.message, - code: err.code, - })); - - logger.warn( - logger.warn( - `Tactic validation failed for ${tacticData.stix.id}: ${JSON.stringify(errors)}`, - ), - ); - - return res.status(400).json({ - error: 'Validation Error', - message: 'Tactic does not meet ATT&CK Data Model requirements', - details: errors, - }); - } - return next(); - } catch (err) { - logger.error(`Tactic validation error: ${err.message}`); - return res.status(500).send('Unable to validate tactic. Server error.'); - } - } - - try { - // Check if we're updating an existing tactic - const isUpdate = req.params.stixId && req.params.modified; - - if (isUpdate) { - const existingTactic = await tacticsService.retrieveVersionById( - req.params.stixId, - req.params.modified, - ); - - // Only validate if transitioning from 'work-in-progress' to 'awaiting-review' - if (!workflowStates.isTransitioningToReview(tacticData, existingTactic)) { - return next(); - } - } - - // Validate the tactic object using the ATT&CK Data Model - logger.debug(`Validating tactic ${tacticData.stix.id} against ATT&CK Data Model`); - - // Use the partial schema to validate only the fields provided in req.body.stix - const validationResult = tacticSchema.safeParse(tacticData.stix); - - // Use the ATT&CK Data Model's tactic schema to validate - // This uses Zod for validation with robust error reporting - // const validationResult = tacticSchema.safeParse(tacticData.stix); - - if (!validationResult.success) { - // Format Zod errors into a more readable structure - const errors = validationResult.error.issues.map((err) => ({ - path: err.path.join('.'), - message: err.message, - code: err.code, - })); - - logger.warn(`Tactic validation failed for ${tacticData.stix.id}: ${JSON.stringify(errors)}`); - - return res.status(400).json({ - error: 'Validation Error', - message: 'Tactic does not meet ATT&CK Data Model requirements', - details: errors, - }); - } - - logger.debug( - `Tactic ${tacticData.stix.id} successfully validated against ATT&CK Data Model schema`, - ); - return next(); - } catch (err) { - logger.error(`Tactic validation error: ${err.message}`); - return res.status(500).send('Unable to validate tactic. Server error.'); - } -}; From 4cf91e01de3555a882dcfd76ce4cf821b31502f5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:07:10 -0400 Subject: [PATCH 011/370] refactor: delete workflow-states module --- app/dtos/workflow-states.js | 70 ------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 app/dtos/workflow-states.js diff --git a/app/dtos/workflow-states.js b/app/dtos/workflow-states.js deleted file mode 100644 index 5bdd7f2c..00000000 --- a/app/dtos/workflow-states.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -/** - * Enum for ATT&CK object workflow states - * @readonly - * @enum {string} - */ -const WorkflowStates = { - /** Initial state for objects being developed */ - WORK_IN_PROGRESS: 'work-in-progress', - - /** State for objects ready for review by leads */ - AWAITING_REVIEW: 'awaiting-review', - - /** State for objects that have been reviewed and approved */ - REVIEWED: 'reviewed', - - /** State for objects that should not be modified (typically imported/system objects) */ - STATIC: 'static', -}; - -/** - * Check if a state transition is valid - * @param {string} fromState - Current workflow state - * @param {string} toState - Target workflow state - * @returns {boolean} - Whether the transition is valid - */ -const isValidStateTransition = (fromState, toState) => { - // If states are the same, it's not a transition - if (fromState === toState) { - return false; - } - - // Define allowed transitions - const allowedTransitions = { - [WorkflowStates.WORK_IN_PROGRESS]: [WorkflowStates.AWAITING_REVIEW], - [WorkflowStates.AWAITING_REVIEW]: [ - WorkflowStates.WORK_IN_PROGRESS, // Return to work-in-progress if changes needed - WorkflowStates.REVIEWED, // Approve after review - ], - [WorkflowStates.REVIEWED]: [ - WorkflowStates.WORK_IN_PROGRESS, // Reopen for edits if needed - ], - [WorkflowStates.STATIC]: [], // Static objects cannot transition to other states - }; - - // Check if the transition is allowed - return allowedTransitions[fromState]?.includes(toState) || false; -}; - -/** - * Check if an object is transitioning from work-in-progress to awaiting-review - * @param {Object} newObject - The updated object state - * @param {Object} oldObject - The previous object state - * @returns {boolean} - True if the object is transitioning to awaiting-review - */ -const isTransitioningToReview = (newObject, oldObject) => { - const newState = newObject?.workspace?.workflow?.state; - const oldState = oldObject?.workspace?.workflow?.state; - - return ( - newState === WorkflowStates.AWAITING_REVIEW && oldState === WorkflowStates.WORK_IN_PROGRESS - ); -}; - -module.exports = { - WorkflowStates, - isValidStateTransition, - isTransitioningToReview, -}; From 44ed739deaa36e9e8e219f76e88200168018b9dd Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:08:15 -0400 Subject: [PATCH 012/370] refactor: validation endpoint (router, controller, service) --- app/controllers/validate-controller.js | 71 +++--- app/routes/validate-routes.js | 6 +- app/services/validate-service.js | 301 +++++++++++++++++-------- 3 files changed, 248 insertions(+), 130 deletions(-) diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js index b13a0dc7..542f70e9 100644 --- a/app/controllers/validate-controller.js +++ b/app/controllers/validate-controller.js @@ -1,46 +1,43 @@ 'use strict'; +const { z } = require('zod'); +const { StatusCodes } = require('http-status-codes'); +const validateService = require('../services/validate-service'); const logger = require('../lib/logger'); -const ValidationService = require('../services/validate-service'); -const validateService = new ValidationService(); +const validationRequestSchema = z.object({ + type: z.string(), + status: z.enum(['work-in-progress', 'awaiting-review', 'reviewed', 'static']), + stix: z.object({}).loose(), +}); + +/** + * Controller for validating STIX objects + * @param {Object} req - Express request object + * @param {Object} res - Express response object + */ exports.validate = async function (req, res) { + logger.debug(req.body); try { - const { type, status, stix } = req.body || {}; - - // Basic request validation - if ( - typeof type !== 'string' || - typeof status !== 'string' || - typeof stix !== 'object' || - stix === null || - Array.isArray(stix) - ) { - logger.warn('Invalid request body for /api/validate'); - return res.status(400).json({ - errors: [ - { - code: 'invalid_request', - path: [], - message: - 'Request body must have a string "type", string "status" and an object "stix".', - }, - ], - }); - } - - // Validate the STIX object using the instance method - const result = validateService.validate(type, status, stix); - - if (result.errors.length && result.errors[0].code === 'unknown_type') { - logger.warn(`Unknown STIX type: ${type}`); - return res.status(400).json(result); + // Validate request structure + const requestResult = validationRequestSchema.safeParse(req.body); + if (!requestResult.success) { + const errors = + requestResult.error.issues?.map((issue) => ({ + message: `${issue.path?.join('.') || 'root'} is ${issue.message}`, + path: issue.path, + code: issue.code, + })) || []; + logger.error('Cannot validate request body', { errors }); + return res.status(StatusCodes.BAD_REQUEST).json({ errors }); + } else { + const result = validateService.validateStixObject(req.body); + res.json(result); } - - logger.debug(`Validation completed for type: ${type}`); - return res.status(200).json(result); - } catch (err) { - logger.error('Failed to validate STIX object: ' + err); - return res.status(500).send('Unable to validate STIX object. Server error.'); + } catch (error) { + console.error('Validation controller error:', error); + res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ + error: 'Internal Server Error', + }); } }; diff --git a/app/routes/validate-routes.js b/app/routes/validate-routes.js index e04c1518..c8e229f3 100644 --- a/app/routes/validate-routes.js +++ b/app/routes/validate-routes.js @@ -3,13 +3,9 @@ const express = require('express'); const validateController = require('../controllers/validate-controller'); -const authn = require('../lib/authn-middleware'); -const authz = require('../lib/authz-middleware'); const router = express.Router(); -router - .route('/validate') - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), validateController.validate); +router.route('/validate').post(validateController.validate); module.exports = router; diff --git a/app/services/validate-service.js b/app/services/validate-service.js index 3cb2970f..dafbf090 100644 --- a/app/services/validate-service.js +++ b/app/services/validate-service.js @@ -1,112 +1,237 @@ 'use strict'; -const yaml = require('js-yaml'); -const fs = require('fs'); const { + tacticSchema, techniqueSchema, + groupSchema, + malwareSchema, + toolSchema, + mitigationSchema, + assetSchema, + dataSourceSchema, campaignSchema, - tacticSchema, - // Add more schemas as needed + dataComponentSchema, + detectionStrategySchema, + analyticSchema, + matrixSchema, } = require('@mitre-attack/attack-data-model'); -const logger = require('../lib/logger'); +const STIX_SCHEMAS = { + 'x-mitre-tactic': tacticSchema, + 'attack-pattern': techniqueSchema, + 'intrusion-set': groupSchema, + malware: malwareSchema, + tool: toolSchema, + campaign: campaignSchema, + 'course-of-action': mitigationSchema, + 'x-mitre-asset': assetSchema, + 'x-mitre-data-source': dataSourceSchema, + 'x-mitre-data-component': dataComponentSchema, + 'x-mitre-detection-strategy': detectionStrategySchema, + 'x-mitre-analytic': analyticSchema, + 'x-mitre-matrix': matrixSchema, +}; -function listToDict(list) { - return Object.fromEntries(list.map((prop) => [prop, true])); -} +/** + * Configuration for transforming validation errors (to warnings or suppression) + * Add new rules here to convert specific validation errors to warnings or suppress them entirely + * + * stixType can be: + * - A single STIX type string (e.g., 'x-mitre-tactic') + * - An array of STIX types (e.g., ['attack-pattern', 'x-mitre-tactic']) + * - 'all' to match any STIX type + */ +const ERROR_TRANSFORMATION_RULES = [ + // x_mitre_modified_by_ref is handled by the backend + { + fieldPath: ['stix', 'x_mitre_modified_by_ref'], + errorCode: 'invalid_value', + stixType: 'all', + suppressError: true, + }, + // Just raise a warning for x_mitre_shortname, letting users know that the value may not comply with the ADM predefined tactic names + { + fieldPath: ['stix', 'x_mitre_shortname'], + errorCode: 'invalid_value', + stixType: 'x-mitre-tactic', + warningMessage: + 'Tactic shortname does not match predefined ATT&CK tactics. This may prevent compatibility with official ATT&CK data but can be used for custom taxonomies.', + }, + // Users cannot set domain membership on some objects - in such cases, x_mitre_domains is set when the content in a STIX bundle + { + fieldPath: ['stix', 'x_mitre_domains'], + errorCode: 'invalid_type', + stixType: ['intrusion-set', 'campaign', 'x-mitre-matrix'], + suppressError: true, + }, + // Users cannot set x_mitre_attack_spec_version - this is handled by the backend + { + fieldPath: ['stix', 'x_mitre_attack_spec_version'], + errorCode: 'invalid_type', + stixType: 'all', + suppressError: true, + }, + // Users cannot set object_marking_refs on campaigns + { + fieldPath: ['stix', 'object_marking_refs'], + errorCode: 'invalid_type', + stixType: ['campaign', 'identity'], + suppressError: true, + }, + { + fieldPath: ['stix', 'created_by_ref'], + errorCode: 'invalid_type', + stixType: ['campaign', 'x-mitre-matrix', 'x-mitre-asset', 'course-of-action'], + suppressError: true, + }, + // Add more rules here as needed +]; +exports.ERROR_TRANSFORMATION_RULES = ERROR_TRANSFORMATION_RULES; -// Utility: Extract required fields for a schema from YAML (handles allOf) -function getRequiredFields(doc, schemaName) { - const schema = doc.components.schemas[schemaName]; - if (!schema) throw new Error(`Schema "${schemaName}" not found`); - let required = []; +/** + * Check if a validation error should be transformed (converted to warning or suppressed) + * @param {Object} error - The validation error from Zod + * @param {string} stixType - The STIX type being validated + * @returns {Object|null} The rule that matches, or null if no transformation should occur + */ +function shouldTransformError(error, stixType) { + for (const rule of ERROR_TRANSFORMATION_RULES) { + // Validate that suppressError and warningMessage are mutually exclusive + if (rule.suppressError && rule.warningMessage !== undefined && rule.warningMessage !== '') { + console.warn( + 'Rule has both suppressError and warningMessage set. suppressError takes precedence.', + ); + } - if (schema.required) { - required = schema.required; - } - // Handle allOf - if (schema.allOf) { - for (const item of schema.allOf) { - if (item.required) { - required = required.concat(item.required); + // Check if stixType matches (if specified in rule) + if (rule.stixType) { + // Handle 'all' case + if (rule.stixType === 'all') { + // Match any STIX type + } else if (Array.isArray(rule.stixType)) { + // Check if current stixType is in the array + if (!rule.stixType.includes(stixType)) { + continue; + } + } else if (rule.stixType !== stixType) { + // Single string comparison + continue; } } + + // Check if field path matches (if specified in rule) + if (rule.fieldPath && JSON.stringify(rule.fieldPath) !== JSON.stringify(error.path)) { + continue; + } + + // Check if error code matches (if specified in rule) + if (rule.errorCode && rule.errorCode !== error.code) { + continue; + } + + // All specified criteria match + return rule; } - return required; + return null; } -class ValidationService { - constructor() { - // Map STIX types to schemas - this.stixSchemas = { - 'attack-pattern': techniqueSchema, - campaign: campaignSchema, - 'x-mitre-tactic': tacticSchema, - }; +exports.shouldTransformError = shouldTransformError; + +/** + * Process validation issues and separate them into errors and warnings + * @param {Array} issues - Zod validation issues + * @param {string} stixType - The STIX type being validated + * @param {string} pathPrefix - Prefix to add to error paths (e.g., 'stix') + * @returns {Object} Object with errors and warnings arrays + */ +function processValidationIssues(issues, stixType, pathPrefix = '') { + const errors = []; + const warnings = []; - this.stixToAttack = { - 'attack-pattern': 'techniques', - campaign: 'campaigns', - 'x-mitre-tactic': 'tactics', + (issues || []).forEach((issue) => { + const fullPath = pathPrefix ? [pathPrefix, ...issue.path] : issue.path; + const errorData = { + message: `${fullPath.join('.')} is ${issue.message}`, + path: fullPath, + code: issue.code, + input: issue.input, }; - } - /** - * Validate a STIX object against its schema. - * @param {string} type - The STIX type (e.g., 'attack-pattern', 'campaign') - * @param {object} status - The workflow status of the object "work-in-progress" || "awaiting-reviewed" || "reviewed" - * @param {object} stix - The STIX object to validate - * @returns {object} { errors: Array } - */ - validate(type, status, stix) { - const attackType = this.stixToAttack[type]; - if (!attackType) { - return { errors: [`Unknown STIX type: ${type}`] }; - } - const file = fs.readFileSync(`app/api/definitions/components/${attackType}.yml`, 'utf8'); - const doc = yaml.load(file); - const req = getRequiredFields(doc, stix.type + '-stix-object'); - const props = req; - const dict = listToDict(props); - const schema = this.stixSchemas[type]; - if (!schema) { - logger.warn(`Unknown STIX type: ${type}`); - return { - errors: [ - { - code: 'unknown_type', - path: ['type'], - message: `Unknown STIX type: ${type}`, - }, - ], - }; - } + const transformationRule = shouldTransformError(errorData, stixType); - const omit_dict = { x_mitre_modified_by_ref: true, x_mitre_attack_spec_version: true }; - if (stix.type == 'campaign' || stix.type == 'intrusion-set') { - omit_dict.x_mitre_domains = true; + if (transformationRule) { + // Check if error should be suppressed (suppressError takes precedence) + if (transformationRule.suppressError) { + // Suppress the error entirely - don't add to errors or warnings + return; + } else if (transformationRule.warningMessage !== undefined) { + // Convert error to warning + warnings.push({ + message: transformationRule.warningMessage || errorData.message, + path: errorData.path, + code: errorData.code, + input: issue.input, + }); + } else { + // Fallback - keep as error if no valid transformation + errors.push(errorData); + } + } else { + // Keep as error + errors.push(errorData); } + }); - let result; + return { errors, warnings }; +} +exports.processValidationIssues = processValidationIssues; - if (status == 'work-in-progress') { - result = schema.partial().required(dict).safeParse(stix); - } else { - result = schema.omit(omit_dict).safeParse(stix); - } +/** + * Validates a STIX object based on its type and status + * @param {Object} payload - The request body + * @param {string} payload.type - STIX object type + * @param {string} payload.status - Validation strictness level + * @param {Object} payload.stix - STIX object data to validate + * @returns {Object} Validation result with valid flag and errors/data + */ +exports.validateStixObject = function (payload) { + const { type, status, stix } = payload; - if (result.success) { - return { errors: [] }; - } else { - // Map Zod errors to your error format - return { - errors: result.error.issues.map((err) => ({ - code: err.code, - path: err.path, - message: err.message, - })), - }; - } + // Check if STIX type is supported + const baseSchema = STIX_SCHEMAS[type]; + if (!baseSchema) { + return { + valid: false, + errors: [ + { + message: `Unknown STIX type: ${type}`, + path: ['type'], + code: 'custom', + input: type, + }, + ], + }; } -} -module.exports = ValidationService; + // Apply partial validation for work-in-progress + const stixSchema = status === 'work-in-progress' ? baseSchema.partial() : baseSchema; + + // Validate STIX data + const stixResult = stixSchema.safeParse(stix); + // const stixResult = baseSchema.safeParse(stix); + + if (stixResult.success) { + return { + valid: true, + data: stixResult.data, + }; + } + + // Process validation errors and separate them into errors and warnings + const { errors, warnings } = processValidationIssues(stixResult.error.issues, type, 'stix'); + + return { + valid: errors.length === 0, // Valid if no blocking errors (warnings are OK) + errors, + warnings, + }; +}; From 5b16158794188fa7cd9a08879f529a6dc5a8a2a1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:08:59 -0400 Subject: [PATCH 013/370] refactor: add validation middleware to all post and put endpoints --- app/routes/analytics-routes.js | 17 +++++++++++++++-- app/routes/assets-routes.js | 17 +++++++++++++++-- app/routes/campaigns-routes.js | 17 +++++++++++++++-- app/routes/collections-routes.js | 10 +++++++++- app/routes/data-components-routes.js | 5 +++++ app/routes/data-sources-routes.js | 11 ++++++++++- app/routes/detection-strategies-routes.js | 6 ++++++ app/routes/groups-routes.js | 18 ++++++++++++++++-- app/routes/identities-routes.js | 17 +++++++++++++++-- app/routes/matrices-routes.js | 18 ++++++++++++++++-- app/routes/mitigations-routes.js | 11 ++++++++++- app/routes/relationships-routes.js | 5 +++++ app/routes/tactics-routes.js | 13 ++++++++++--- app/routes/techniques-routes.js | 17 +++++++++++++++-- 14 files changed, 162 insertions(+), 20 deletions(-) diff --git a/app/routes/analytics-routes.js b/app/routes/analytics-routes.js index da15572d..a20c0a00 100644 --- a/app/routes/analytics-routes.js +++ b/app/routes/analytics-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { analyticSchema } = require('@mitre-attack/attack-data-model'); + const analyticsController = require('../controllers/analytics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), analyticsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), analyticsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(analyticSchema), + analyticsController.create, + ); router .route('/analytics/:stixId') @@ -33,7 +41,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), analyticsController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), analyticsController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(analyticSchema), + analyticsController.updateFull, + ) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/routes/assets-routes.js b/app/routes/assets-routes.js index bc434fe7..3bf477e7 100644 --- a/app/routes/assets-routes.js +++ b/app/routes/assets-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { assetSchema } = require('@mitre-attack/attack-data-model'); + const assetsController = require('../controllers/assets-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), assetsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(assetSchema), + assetsController.create, + ); router .route('/assets/:stixId') @@ -33,7 +41,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), assetsController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(assetSchema), + assetsController.updateFull, + ) .delete(authn.authenticate, authz.requireRole(authz.admin), assetsController.deleteVersionById); module.exports = router; diff --git a/app/routes/campaigns-routes.js b/app/routes/campaigns-routes.js index efdb09fb..f51c14d6 100644 --- a/app/routes/campaigns-routes.js +++ b/app/routes/campaigns-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { campaignSchema } = require('@mitre-attack/attack-data-model'); + const campaignsController = require('../controllers/campaigns-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), campaignsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(campaignSchema), + campaignsController.create, + ); router .route('/campaigns/:stixId') @@ -33,7 +41,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), campaignsController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(campaignSchema), + campaignsController.updateFull, + ) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/routes/collections-routes.js b/app/routes/collections-routes.js index 725dddde..60ce9fdb 100644 --- a/app/routes/collections-routes.js +++ b/app/routes/collections-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { collectionSchema } = require('@mitre-attack/attack-data-model'); + const collectionsController = require('../controllers/collections-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), collectionsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), collectionsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(collectionSchema), + collectionsController.create, + ); router .route('/collections/:stixId') diff --git a/app/routes/data-components-routes.js b/app/routes/data-components-routes.js index ac2a4237..624ede6a 100644 --- a/app/routes/data-components-routes.js +++ b/app/routes/data-components-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { dataComponentSchema } = require('@mitre-attack/attack-data-model'); + const dataComponentsController = require('../controllers/data-components-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -18,6 +21,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(dataComponentSchema), dataComponentsController.create, ); @@ -40,6 +44,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(dataComponentSchema), dataComponentsController.updateFull, ) .delete( diff --git a/app/routes/data-sources-routes.js b/app/routes/data-sources-routes.js index f220850f..c76c3dc4 100644 --- a/app/routes/data-sources-routes.js +++ b/app/routes/data-sources-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { dataSourceSchema } = require('@mitre-attack/attack-data-model'); + const dataSourcesController = require('../controllers/data-sources-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), dataSourcesController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), dataSourcesController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(dataSourceSchema), + dataSourcesController.create, + ); router .route('/data-sources/:stixId') @@ -36,6 +44,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(dataSourceSchema), dataSourcesController.updateFull, ) .delete( diff --git a/app/routes/detection-strategies-routes.js b/app/routes/detection-strategies-routes.js index 97d27600..823c9cae 100644 --- a/app/routes/detection-strategies-routes.js +++ b/app/routes/detection-strategies-routes.js @@ -2,10 +2,14 @@ const express = require('express'); +const { detectionStrategySchema } = require('@mitre-attack/attack-data-model'); + const detectionStrategiesController = require('../controllers/detection-strategies-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); + const router = express.Router(); router @@ -18,6 +22,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(detectionStrategySchema), detectionStrategiesController.create, ); @@ -44,6 +49,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(detectionStrategySchema), detectionStrategiesController.updateFull, ) .delete( diff --git a/app/routes/groups-routes.js b/app/routes/groups-routes.js index 41b0ad9d..dd82c97e 100644 --- a/app/routes/groups-routes.js +++ b/app/routes/groups-routes.js @@ -2,10 +2,14 @@ const express = require('express'); +const { groupSchema } = require('@mitre-attack/attack-data-model'); + const groupsController = require('../controllers/groups-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); + const router = express.Router(); router @@ -15,7 +19,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), groupsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(groupSchema), + groupsController.create, + ); router .route('/groups/:stixId') @@ -33,7 +42,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), groupsController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(groupSchema), + groupsController.updateFull, + ) .delete(authn.authenticate, authz.requireRole(authz.admin), groupsController.deleteVersionById); module.exports = router; diff --git a/app/routes/identities-routes.js b/app/routes/identities-routes.js index 6f80d5dd..0c0fa80f 100644 --- a/app/routes/identities-routes.js +++ b/app/routes/identities-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { identitySchema } = require('@mitre-attack/attack-data-model'); + const identitiesController = require('../controllers/identities-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), identitiesController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), identitiesController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(identitySchema), + identitiesController.create, + ); router .route('/identities/:stixId') @@ -33,7 +41,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), identitiesController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), identitiesController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(identitySchema), + identitiesController.updateFull, + ) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/routes/matrices-routes.js b/app/routes/matrices-routes.js index 20a27c32..7682418d 100644 --- a/app/routes/matrices-routes.js +++ b/app/routes/matrices-routes.js @@ -2,10 +2,14 @@ const express = require('express'); +const { matrixSchema } = require('@mitre-attack/attack-data-model'); + const matricesController = require('../controllers/matrices-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); + const router = express.Router(); router @@ -15,7 +19,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), matricesController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(matrixSchema), + matricesController.create, + ); router .route('/matrices/:stixId') @@ -33,7 +42,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), matricesController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(matrixSchema), + matricesController.updateFull, + ) .delete(authn.authenticate, authz.requireRole(authz.admin), matricesController.deleteVersionById); router diff --git a/app/routes/mitigations-routes.js b/app/routes/mitigations-routes.js index 04b7e234..9d678f31 100644 --- a/app/routes/mitigations-routes.js +++ b/app/routes/mitigations-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { mitigationSchema } = require('@mitre-attack/attack-data-model'); + const mitigationsController = require('../controllers/mitigations-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), mitigationsController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), mitigationsController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(mitigationSchema), + mitigationsController.create, + ); router .route('/mitigations/:stixId') @@ -36,6 +44,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(mitigationSchema), mitigationsController.updateFull, ) .delete( diff --git a/app/routes/relationships-routes.js b/app/routes/relationships-routes.js index 341c0644..08e44efa 100644 --- a/app/routes/relationships-routes.js +++ b/app/routes/relationships-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { relationshipSchema } = require('@mitre-attack/attack-data-model'); + const relationshipsController = require('../controllers/relationships-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -18,6 +21,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(relationshipSchema), relationshipsController.create, ); @@ -40,6 +44,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), + // validateWorkspaceStixData(relationshipSchema), relationshipsController.updateFull, ) .delete( diff --git a/app/routes/tactics-routes.js b/app/routes/tactics-routes.js index 7ebe9e02..834006c1 100644 --- a/app/routes/tactics-routes.js +++ b/app/routes/tactics-routes.js @@ -2,10 +2,12 @@ const express = require('express'); +const { tacticSchema } = require('@mitre-attack/attack-data-model'); + const tacticsController = require('../controllers/tactics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const validateTacticForSpecCompliance = require('../middleware/tactic-validation-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -19,7 +21,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateTacticForSpecCompliance, + validateWorkspaceStixData(tacticSchema), tacticsController.create, ); @@ -39,7 +41,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), tacticsController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(tacticSchema), + tacticsController.updateFull, + ) .delete(authn.authenticate, authz.requireRole(authz.admin), tacticsController.deleteVersionById); router diff --git a/app/routes/techniques-routes.js b/app/routes/techniques-routes.js index c9908aac..6707df83 100644 --- a/app/routes/techniques-routes.js +++ b/app/routes/techniques-routes.js @@ -2,9 +2,12 @@ const express = require('express'); +const { techniqueSchema } = require('@mitre-attack/attack-data-model'); + const techniquesController = require('../controllers/techniques-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +18,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), techniquesController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(techniqueSchema), + techniquesController.create, + ); router .route('/techniques/:stixId') @@ -33,7 +41,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), techniquesController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(techniqueSchema), + techniquesController.updateFull, + ) .delete( authn.authenticate, authz.requireRole(authz.admin), From 5c501c485d77235698be3b86e8ab0c7a3a5bc201 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:10:42 -0400 Subject: [PATCH 014/370] build: upgrade ADM and install http-status-codes --- package-lock.json | 24 ++++++++++++++++-------- package.json | 6 ++++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5ff6a44b..0d4fbbd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.0.1", + "@mitre-attack/attack-data-model": "^4.4.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", @@ -21,6 +21,7 @@ "express-openapi-validator": "^5.5.4", "express-session": "^1.18.2", "helmet": "^8.1.0", + "http-status-codes": "^2.3.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.2.0", "jwt-decode": "^4.0.0", @@ -39,7 +40,8 @@ "superagent": "^10.2.2", "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", - "winston": "^3.17.0" + "winston": "^3.17.0", + "zod": "^4.1.8" }, "devDependencies": { "@commitlint/cli": "^19.8.1", @@ -2011,9 +2013,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.0.1.tgz", - "integrity": "sha512-SGSIPKz75LV6Blpf8Le0rapvIjldLSl6UM+TYEVt4vo/n1Bk/bsh/kdBYH4QBQPoNvpM+ftO12YKJwSHWDr96Q==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.4.0.tgz", + "integrity": "sha512-VMECVin0+bX/O+y+J4fqoB2IJh1At4GjxuaDQH7v8KpLLW73sjtFT+05huSd5LGMHp14kXtlzes8YpsDXpFQ/g==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", @@ -6321,6 +6323,12 @@ "dev": true, "license": "MIT" }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "dev": true, @@ -13853,9 +13861,9 @@ } }, "node_modules/zod": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz", - "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 01efe532..b43c02eb 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.0.1", + "@mitre-attack/attack-data-model": "^4.4.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", @@ -49,6 +49,7 @@ "express-openapi-validator": "^5.5.4", "express-session": "^1.18.2", "helmet": "^8.1.0", + "http-status-codes": "^2.3.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.2.0", "jwt-decode": "^4.0.0", @@ -67,7 +68,8 @@ "superagent": "^10.2.2", "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", - "winston": "^3.17.0" + "winston": "^3.17.0", + "zod": "^4.1.8" }, "devDependencies": { "@commitlint/cli": "^19.8.1", From a358b40daa2dc9f39b14760ba21ecf245f78f28e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:36:53 -0400 Subject: [PATCH 015/370] feat: add relationship, marking def, and collection support to the validate service --- app/services/validate-service.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/services/validate-service.js b/app/services/validate-service.js index dafbf090..895bb83e 100644 --- a/app/services/validate-service.js +++ b/app/services/validate-service.js @@ -14,6 +14,9 @@ const { detectionStrategySchema, analyticSchema, matrixSchema, + relationshipSchema, + collectionSchema, + markingDefinitionSchema, } = require('@mitre-attack/attack-data-model'); const STIX_SCHEMAS = { @@ -23,13 +26,16 @@ const STIX_SCHEMAS = { malware: malwareSchema, tool: toolSchema, campaign: campaignSchema, + relationship: relationshipSchema, 'course-of-action': mitigationSchema, + 'marking-definition': markingDefinitionSchema, 'x-mitre-asset': assetSchema, 'x-mitre-data-source': dataSourceSchema, 'x-mitre-data-component': dataComponentSchema, 'x-mitre-detection-strategy': detectionStrategySchema, 'x-mitre-analytic': analyticSchema, 'x-mitre-matrix': matrixSchema, + 'x-mitre-collection': collectionSchema, }; /** @@ -61,7 +67,7 @@ const ERROR_TRANSFORMATION_RULES = [ { fieldPath: ['stix', 'x_mitre_domains'], errorCode: 'invalid_type', - stixType: ['intrusion-set', 'campaign', 'x-mitre-matrix'], + stixType: ['intrusion-set', 'campaign', 'x-mitre-matrix', 'x-mitre-detection-strategy'], suppressError: true, }, // Users cannot set x_mitre_attack_spec_version - this is handled by the backend From e1a16892229a11adb17dd9c996811d77bec3e546 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:37:34 -0400 Subject: [PATCH 016/370] feat: implement poc for validating tool and software --- app/lib/validation-middleware.js | 75 ++++++++++++++++++++++++++------ app/routes/software-routes.js | 16 ++++++- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index c0aa07be..12ae635c 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -8,6 +8,7 @@ const { createAttackIdSchema, stixTypeToAttackIdMapping, } = require('@mitre-attack/attack-data-model/dist/schemas/common/attack-id'); +const { toolSchema, malwareSchema } = require('@mitre-attack/attack-data-model'); /** * Basic workspace schema (without rigid attack ID validation) @@ -57,6 +58,27 @@ function createWorkspaceSchema(stixType) { return workspaceSchema; } +function extractStringLiteralFromStixTypeZodSchema(zodSchema) { + // Method 1: Direct shape access (works for most schemas) + if (zodSchema.shape?.type?.def?.values?.[0]) { + return zodSchema.shape.type.def.values[0]; + } + // Method 2: Through _zod.def.in.def (works for schemas with .transform()) + else if (zodSchema._zod?.def?.in?.def?.shape?.type?.def?.values?.[0]) { + return zodSchema._zod.def.in.def.shape.type.def.values[0]; + } + // Method 3: Works for schemas that support multiple types, i.e., softwareSchema -> [tool, malware] + else if (zodSchema.shape?.type.def.options) { + const stixTypes = []; + for (const opt of zodSchema.shape.type.def.options) { + stixTypes.push(opt.def.values[0]); + } + return stixTypes; + } else { + throw new Error('Could not extract STIX type from schema'); + } +} + /** * Factory function that creates a combined workspace+STIX schema with conditional partial validation * @param {z.ZodObject} stixSchema - The STIX object schema to validate against @@ -80,18 +102,7 @@ function createWorkspaceStixSchema( try { // Extract the STIX type from the schema with fallback for transformed schemas - let stixTypeStringLiteral; - - // Method 1: Direct shape access (works for most schemas) - if (stixSchema.shape?.type?.def?.values?.[0]) { - stixTypeStringLiteral = stixSchema.shape.type.def.values[0]; - } - // Method 2: Through _zod.def.in.def (works for relationshipSchema with .transform()) - else if (stixSchema._zod?.def?.in?.def?.shape?.type?.def?.values?.[0]) { - stixTypeStringLiteral = stixSchema._zod.def.in.def.shape.type.def.values[0]; - } else { - throw new Error('Could not extract STIX type from schema'); - } + const stixTypeStringLiteral = extractStringLiteralFromStixTypeZodSchema(stixSchema); logger.debug('Extracted STIX type from schema:', { stixTypeStringLiteral }); @@ -152,6 +163,7 @@ function createWorkspaceStixSchema( function validateWorkspaceStixData(stixSchema) { return (req, res, next) => { logger.debug('Starting workspace+STIX validation middleware'); + logger.debug('Request body structure:', { hasWorkspace: !!req.body?.workspace, hasStix: !!req.body?.stix, @@ -167,8 +179,45 @@ function validateWorkspaceStixData(stixSchema) { isDefault: !req.body?.workspace?.workflow?.state, }); + // Override softwareSchema --> toolSchema OR malwareSchema IF stixSchema is softwareSchema + // Basically, we want to validate with the more specific schema if possible + const stixTypeStringLiteral = extractStringLiteralFromStixTypeZodSchema(stixSchema); + logger.debug('Extracted STIX type string literal for schema override:', { + stixTypeStringLiteral, + }); + + let finalSchema = stixSchema; + + // Edge case handling + // We can't infer which schema to use from the software endpoint alone + // In this case we also need to check the type field of the request body itself + if ( + Array.isArray(stixTypeStringLiteral) && + stixTypeStringLiteral.length === 2 && + stixTypeStringLiteral.includes('tool') && + stixTypeStringLiteral.includes('malware') + ) { + const requestStixType = req.body.stix.type; + logger.debug('Software schema detected, checking for specific type override:', { + requestStixType, + availableTypes: ['tool', 'malware'], + }); + + if (requestStixType === 'tool') { + logger.debug('Overriding softwareSchema with toolSchema'); + finalSchema = toolSchema; + } else if (requestStixType === 'malware') { + logger.debug('Overriding softwareSchema with malwareSchema'); + finalSchema = malwareSchema; + } else { + throw new Error( + `Schema override logic encountered unexpected condition. STIX type: ${requestStixType}, Available types: [tool, malware]. This error indicates a logic flow issue in schema selection.`, + ); + } + } + // Create schema with conditional validation based on workflow state - const combinedSchema = createWorkspaceStixSchema(stixSchema, workflowState); + const combinedSchema = createWorkspaceStixSchema(finalSchema, workflowState); logger.debug('Attempting to parse request body with combined schema'); combinedSchema.parse(req.body); diff --git a/app/routes/software-routes.js b/app/routes/software-routes.js index c1707464..026608d2 100644 --- a/app/routes/software-routes.js +++ b/app/routes/software-routes.js @@ -1,10 +1,12 @@ 'use strict'; const express = require('express'); +const { softwareSchema } = require('@mitre-attack/attack-data-model'); const softwareController = require('../controllers/software-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); +const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -15,7 +17,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), softwareController.retrieveAll, ) - .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.create); + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(softwareSchema), + softwareController.create, + ); router .route('/software/:stixId') @@ -33,7 +40,12 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), softwareController.retrieveVersionById, ) - .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.updateFull) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + validateWorkspaceStixData(softwareSchema), + softwareController.updateFull, + ) .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteVersionById); module.exports = router; From 8d79d6bc5c8a4e2df3f1b46c58e1d3201668f417 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:01:56 -0400 Subject: [PATCH 017/370] refactor: mod validation middleware to take an array of schemas - middleware now accepts stixSchemasorSchemas (single schema or array of schemas) - software router now passes [toolSchema, malwareSchema] instead of softwareSchema - no more special handling for softwareSchema --- app/lib/validation-middleware.js | 76 +++++++++++++++++--------------- app/routes/software-routes.js | 6 +-- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 12ae635c..2245943f 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -8,7 +8,6 @@ const { createAttackIdSchema, stixTypeToAttackIdMapping, } = require('@mitre-attack/attack-data-model/dist/schemas/common/attack-id'); -const { toolSchema, malwareSchema } = require('@mitre-attack/attack-data-model'); /** * Basic workspace schema (without rigid attack ID validation) @@ -157,10 +156,10 @@ function createWorkspaceStixSchema( /** * Middleware for parsing the request body using a specified STIX schema from the ATT&CK Data Model. * Both the `workspace` and `stix` keys are checked. - * @param {*} stixSchema - * @returns + * @param {z.ZodObject|z.ZodObject[]} stixSchemaOrSchemas - Single schema or array of schemas to validate against + * @returns {Function} Express middleware function */ -function validateWorkspaceStixData(stixSchema) { +function validateWorkspaceStixData(stixSchemaOrSchemas) { return (req, res, next) => { logger.debug('Starting workspace+STIX validation middleware'); @@ -179,41 +178,48 @@ function validateWorkspaceStixData(stixSchema) { isDefault: !req.body?.workspace?.workflow?.state, }); - // Override softwareSchema --> toolSchema OR malwareSchema IF stixSchema is softwareSchema - // Basically, we want to validate with the more specific schema if possible - const stixTypeStringLiteral = extractStringLiteralFromStixTypeZodSchema(stixSchema); - logger.debug('Extracted STIX type string literal for schema override:', { - stixTypeStringLiteral, - }); - - let finalSchema = stixSchema; - - // Edge case handling - // We can't infer which schema to use from the software endpoint alone - // In this case we also need to check the type field of the request body itself - if ( - Array.isArray(stixTypeStringLiteral) && - stixTypeStringLiteral.length === 2 && - stixTypeStringLiteral.includes('tool') && - stixTypeStringLiteral.includes('malware') - ) { - const requestStixType = req.body.stix.type; - logger.debug('Software schema detected, checking for specific type override:', { - requestStixType, - availableTypes: ['tool', 'malware'], - }); + // Determine which schema to use based on request STIX type + const requestStixType = req.body?.stix?.type; + logger.debug('Request STIX type:', { requestStixType }); + + let finalSchema; + + // Handle array of schemas - find the one that matches the request type + if (Array.isArray(stixSchemaOrSchemas)) { + logger.debug('Multiple schemas provided, finding matching schema for request type'); + + for (const schema of stixSchemaOrSchemas) { + try { + const schemaStixType = extractStringLiteralFromStixTypeZodSchema(schema); + logger.debug('Checking schema with type:', { schemaStixType }); + + // Check if this schema matches the request type + if ( + (typeof schemaStixType === 'string' && schemaStixType === requestStixType) || + (Array.isArray(schemaStixType) && schemaStixType.includes(requestStixType)) + ) { + logger.debug('Found matching schema for request type:', { + requestStixType, + schemaStixType, + }); + finalSchema = schema; + break; + } + } catch (error) { + logger.debug('Could not extract type from schema, skipping:', { error: error.message }); + continue; + } + } - if (requestStixType === 'tool') { - logger.debug('Overriding softwareSchema with toolSchema'); - finalSchema = toolSchema; - } else if (requestStixType === 'malware') { - logger.debug('Overriding softwareSchema with malwareSchema'); - finalSchema = malwareSchema; - } else { + if (!finalSchema) { throw new Error( - `Schema override logic encountered unexpected condition. STIX type: ${requestStixType}, Available types: [tool, malware]. This error indicates a logic flow issue in schema selection.`, + `No matching schema found for STIX type: ${requestStixType}. Available schemas: ${stixSchemaOrSchemas.length}`, ); } + } else { + // Single schema - use it directly + logger.debug('Single schema provided, using directly'); + finalSchema = stixSchemaOrSchemas; } // Create schema with conditional validation based on workflow state diff --git a/app/routes/software-routes.js b/app/routes/software-routes.js index 026608d2..6f65040f 100644 --- a/app/routes/software-routes.js +++ b/app/routes/software-routes.js @@ -1,7 +1,7 @@ 'use strict'; const express = require('express'); -const { softwareSchema } = require('@mitre-attack/attack-data-model'); +const { toolSchema, malwareSchema } = require('@mitre-attack/attack-data-model'); const softwareController = require('../controllers/software-controller'); const authn = require('../lib/authn-middleware'); @@ -20,7 +20,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(softwareSchema), + validateWorkspaceStixData([toolSchema, malwareSchema]), softwareController.create, ); @@ -43,7 +43,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(softwareSchema), + validateWorkspaceStixData([toolSchema, malwareSchema]), softwareController.updateFull, ) .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteVersionById); From aa2a9f26ce203523742e768de9efb31ef51c7f66 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:05:17 -0400 Subject: [PATCH 018/370] build: upgrade ADM to 4.4.1 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d4fbbd4..5f209e7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.4.0", + "@mitre-attack/attack-data-model": "^4.4.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", @@ -2013,9 +2013,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.4.0.tgz", - "integrity": "sha512-VMECVin0+bX/O+y+J4fqoB2IJh1At4GjxuaDQH7v8KpLLW73sjtFT+05huSd5LGMHp14kXtlzes8YpsDXpFQ/g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.4.1.tgz", + "integrity": "sha512-B9EghJKepm5J3OUGs+GPpVXGDYqRdqHCF3m3r6+NxG00JmgBiIpRFv8nazaDFLEvxVzmeI984ROenCZkOn/dVw==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index b43c02eb..d8336af6 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.4.0", + "@mitre-attack/attack-data-model": "^4.4.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", From 2f2c70a3d45052f7d81ee90f07585e040ecd132c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:51:22 -0400 Subject: [PATCH 019/370] fix: remove dtos sub-package (artifact from prev poc) --- app/dtos/index.js | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 app/dtos/index.js diff --git a/app/dtos/index.js b/app/dtos/index.js deleted file mode 100644 index af74c6ef..00000000 --- a/app/dtos/index.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -// Export all DTOs from this directory -const workflowStates = require('./workflow-states'); - -module.exports = { - workflowStates, -}; From b2c99db17df00092df02a851168ac24f6a94071a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:56:35 -0400 Subject: [PATCH 020/370] feat: enable ADM validation on relationships PUT endpoint --- app/routes/relationships-routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/relationships-routes.js b/app/routes/relationships-routes.js index 08e44efa..005cc5c5 100644 --- a/app/routes/relationships-routes.js +++ b/app/routes/relationships-routes.js @@ -44,7 +44,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - // validateWorkspaceStixData(relationshipSchema), + validateWorkspaceStixData(relationshipSchema), relationshipsController.updateFull, ) .delete( From f4894cf0922a516ded0d66aa7be8b2586205085e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:57:08 -0400 Subject: [PATCH 021/370] fix: loosen restrictions on validation endpoint response structure --- app/api/definitions/paths/validate-paths.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/api/definitions/paths/validate-paths.yml b/app/api/definitions/paths/validate-paths.yml index 1bc1dafa..6a81ed85 100644 --- a/app/api/definitions/paths/validate-paths.yml +++ b/app/api/definitions/paths/validate-paths.yml @@ -34,20 +34,5 @@ paths: application/json: schema: type: object - properties: - errors: - type: array - items: - type: object - properties: - code: - type: string - example: custom - path: - type: array - items: - type: string - message: - type: string '400': description: 'Invalid request' From bd30abbbfc327ce47bb32bc417a7d95d68df813a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:58:05 -0400 Subject: [PATCH 022/370] fix: revert openapi schema names --- app/api/definitions/components/tactics.yml | 4 ++-- app/api/definitions/components/techniques.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/api/definitions/components/tactics.yml b/app/api/definitions/components/tactics.yml index 590fff33..c28a5fd2 100644 --- a/app/api/definitions/components/tactics.yml +++ b/app/api/definitions/components/tactics.yml @@ -9,9 +9,9 @@ components: workspace: $ref: 'workspace.yml#/components/schemas/workspace' stix: - $ref: '#/components/schemas/x-mitre-tactic-stix-object' + $ref: '#/components/schemas/x-mitre-tactic' - x-mitre-tactic-stix-object: + x-mitre-tactic: allOf: - $ref: 'stix-common.yml#/components/schemas/stix-common' - type: object diff --git a/app/api/definitions/components/techniques.yml b/app/api/definitions/components/techniques.yml index a93e01f0..1562fa12 100644 --- a/app/api/definitions/components/techniques.yml +++ b/app/api/definitions/components/techniques.yml @@ -9,9 +9,9 @@ components: workspace: $ref: 'workspace.yml#/components/schemas/workspace' stix: - $ref: '#/components/schemas/attack-pattern-stix-object' + $ref: '#/components/schemas/attack-pattern' - attack-pattern-stix-object: + attack-pattern: allOf: - $ref: 'stix-common.yml#/components/schemas/stix-common' - type: object From af25e62773199bc335d196d12d0937ff60410e3f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:56:36 -0400 Subject: [PATCH 023/370] chore: update vscode launch.json to set critical env variables for launch config --- .vscode/launch.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2bef47fe..4d01aac9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,11 @@ "skipFiles": ["/**"], "program": "${workspaceFolder}/bin/www", "outputCapture": "std", - "envFile": "${workspaceFolder}/.env" + "envFile": "${workspaceFolder}/.env", + "env": { + "DATABASE_URL": "mongodb://localhost:27017/attack-workspace", + "LOG_LEVEL": "debug" + } }, { "type": "node", From 9ccb78b05826ecf04a07c349151e8459bf105f76 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:02:03 -0400 Subject: [PATCH 024/370] feat(config): add validateRequests runtime config to allow toggling between validation mechanisms - VALIDATE_WITH_ADM_SCHEMAS: when enabled, requests are validated with the ATT&CK Data Model Zod schemas - VALIDATE_WITH_LEGACY_SCHEMAS: when enabled, requests are validated with the preexisting, OpenAPI YAML-based spec files --- app/config/config.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/config/config.js b/app/config/config.js index 0345d229..248b0cff 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -199,6 +199,20 @@ function loadConfig() { default: './app/api/definitions/openapi.yml', }, }, + validateRequests: { + withAttackDataModel: { + doc: 'Enable validation of POST and PUT request bodies using the ATT&CK Data Model', + format: Boolean, + default: false, + env: 'VALIDATE_WITH_ADM_SCHEMAS', + }, + withOpenApi: { + doc: 'Enable validation of POST and PUT request bodies using the legacy OpenAPI YAML-based validation schemas', + format: Boolean, + default: true, + env: 'VALIDATE_WITH_LEGACY_SCHEMAS', + }, + }, collectionIndex: { defaultInterval: { doc: 'How often collection indexes should check for updates (in seconds). Only applies to new indexes added to the REST API, does not affect existing collection indexes', From 25c19416837295ef0583b62bfe15a93f137c54fe Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:03:54 -0400 Subject: [PATCH 025/370] feat(validation-middleware): add global toggle for optionally bypassing ADM validation The ADM validation middlware now reads a runtime config value at request time to determine whether to perform validation --- app/lib/validation-middleware.js | 36 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 2245943f..194d4bf8 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -156,11 +156,21 @@ function createWorkspaceStixSchema( /** * Middleware for parsing the request body using a specified STIX schema from the ATT&CK Data Model. * Both the `workspace` and `stix` keys are checked. - * @param {z.ZodObject|z.ZodObject[]} stixSchemaOrSchemas - Single schema or array of schemas to validate against + * @param {z.ZodObject|z.ZodObject[]} oneOrMoreZodSchemas - Single schema or array of schemas to validate against + * @param {Object} options - Configuration options + * @param {boolean} options.enabled - Whether validation is enabled (defaults to true) * @returns {Function} Express middleware function */ -function validateWorkspaceStixData(stixSchemaOrSchemas) { +function middleware(oneOrMoreZodSchemas, options = {}) { + const { enabled = true } = options; + return (req, res, next) => { + // Skip validation if disabled + if (!enabled) { + logger.debug('Workspace STIX validation is disabled, skipping'); + return next(); + } + logger.debug('Starting workspace+STIX validation middleware'); logger.debug('Request body structure:', { @@ -185,10 +195,10 @@ function validateWorkspaceStixData(stixSchemaOrSchemas) { let finalSchema; // Handle array of schemas - find the one that matches the request type - if (Array.isArray(stixSchemaOrSchemas)) { + if (Array.isArray(oneOrMoreZodSchemas)) { logger.debug('Multiple schemas provided, finding matching schema for request type'); - for (const schema of stixSchemaOrSchemas) { + for (const schema of oneOrMoreZodSchemas) { try { const schemaStixType = extractStringLiteralFromStixTypeZodSchema(schema); logger.debug('Checking schema with type:', { schemaStixType }); @@ -213,13 +223,13 @@ function validateWorkspaceStixData(stixSchemaOrSchemas) { if (!finalSchema) { throw new Error( - `No matching schema found for STIX type: ${requestStixType}. Available schemas: ${stixSchemaOrSchemas.length}`, + `No matching schema found for STIX type: ${requestStixType}. Available schemas: ${oneOrMoreZodSchemas.length}`, ); } } else { // Single schema - use it directly logger.debug('Single schema provided, using directly'); - finalSchema = stixSchemaOrSchemas; + finalSchema = oneOrMoreZodSchemas; } // Create schema with conditional validation based on workflow state @@ -290,6 +300,20 @@ function validateWorkspaceStixData(stixSchemaOrSchemas) { }; } +/** + * Pre-configured validation middleware factory that uses runtime configuration. + * The middleware reads the config value at request time to support dynamic config changes (e.g., during tests). + */ +function validateWorkspaceStixData(oneOrMoreZodSchemas) { + return (req, res, next) => { + // Read config at request time to allow dynamic changes + const config = require('../config/config'); + const enabled = config.validateRequests.withAttackDataModel; + const middlewareFn = middleware(oneOrMoreZodSchemas, { enabled }); + return middlewareFn(req, res, next); + }; +} + module.exports = { /** Express middleware factory for workspace+STIX validation */ validateWorkspaceStixData, From 8829360f0e75d1f0654a76fa609b6c0e0c44534a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:05:40 -0400 Subject: [PATCH 026/370] feat: condition the global OpenApiValidator middleware execution on runtime config parameter - The OpenApiValidator.middleware will attach to the global router ONLY if config.validateRequests.withOpenApi is enabled - The default value for this config parameter is true, so the change is non-breaking --- app/routes/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/routes/index.js b/app/routes/index.js index c6180c81..ab18feb2 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -17,13 +17,15 @@ router.use('/api', bodyParser.json({ limit: '50mb' })); router.use('/api', bodyParser.urlencoded({ limit: '1mb', extended: true })); // Setup request validation -router.use( - OpenApiValidator.middleware({ - apiSpec: config.openApi.specPath, - validateRequests: true, - validateResponses: false, - }), -); +if (config.validateRequests.withOpenApi) { + router.use( + OpenApiValidator.middleware({ + apiSpec: config.openApi.specPath, + validateRequests: true, + validateResponses: false, + }), + ); +} // Setup passport middleware router.use('/api', authnConfiguration.passportMiddleware()); From 60d41c6ebcffb6323e04f8c8550482653b5d8f19 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:09:08 -0400 Subject: [PATCH 027/370] test: add adm-validation.spec.js for checking ADM validation middleware behavior - Checks POST and PUT operations for the techniques service - Checks work-in-progress workflow state (partial validation) - Checks reviewed workflow state (full validation) - Covers both true positive (should pass) and true negative (should throw) states Currently, the test suite passes when run by itself, but fails when run in conjunction with other test suites --- .../api/validation/adm-validation.spec.js | 638 ++++++++++++++++++ 1 file changed, 638 insertions(+) create mode 100644 app/tests/api/validation/adm-validation.spec.js diff --git a/app/tests/api/validation/adm-validation.spec.js b/app/tests/api/validation/adm-validation.spec.js new file mode 100644 index 00000000..35691c91 --- /dev/null +++ b/app/tests/api/validation/adm-validation.spec.js @@ -0,0 +1,638 @@ +const request = require('supertest'); +const { expect } = require('expect'); + +const database = require('../../../lib/database-in-memory'); +const databaseConfiguration = require('../../../lib/database-configuration'); +const config = require('../../../config/config'); +const login = require('../../shared/login'); + +const logger = require('../../../lib/logger'); +logger.level = 'debug'; + +const uuid = require('uuid'); +const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/dist/generator'); + +/** + * Smoke tests for ATT&CK Data Model (ADM) validation middleware. + * + * These tests verify that the ADM validation middleware correctly validates + * POST and PUT requests using the Zod-based schemas from the ADM library. + * + * Test Coverage: + * - POST operations with work-in-progress workflow state (partial validation) + * - POST operations with reviewed workflow state (full validation) + * - PUT operations with work-in-progress workflow state (partial validation) + * - PUT operations with reviewed workflow state (full validation) + * - True positives: valid data should pass + * - True negatives: invalid data should fail with proper errors + * - Validation toggle (enabled/disabled) + * + * NOTE: Tests focus on techniques initially. Once validated, can be generalized to other types. + */ +describe('ADM Validation Middleware (Smoke Tests)', function () { + let app; + let passportCookie; + + const endpoint = '/api/techniques'; + const stixType = 'attack-pattern'; + + /** + * Helper function to create a synthetic STIX object with unique ID and timestamps. + * + * Uses the ADM's createSyntheticStixObject() to generate a valid baseline object, + * then customizes it for testing purposes (unique IDs, fresh timestamps, etc.). + * + * NOTE: This function also includes special handling for x_mitre_platforms and + * x_mitre_contributors to work around a Mongoose serialization issue. See the + * inline comments below for detailed explanation. + */ + function createSyntheticStix(type) { + const syntheticStix = createSyntheticStixObject(type); + if (!syntheticStix) { + throw new Error(`Failed to create synthetic STIX object for type: ${type}`); + } + + // Remove server-managed field + delete syntheticStix.x_mitre_attack_spec_version; + + // Generate unique ID to avoid conflicts between tests + syntheticStix.id = `${type}--${uuid.v4()}`; + + // Set fresh timestamps for each test to avoid conflicts + const timestamp = new Date().toISOString(); + syntheticStix.created = timestamp; + syntheticStix.modified = timestamp; + + // ============================================================================= + // SPECIAL HANDLING FOR x_mitre_platforms AND x_mitre_contributors + // ============================================================================= + // + // The synthetic generator (createSyntheticStixObject) does NOT populate these + // two fields, which causes a problem due to how Mongoose serializes documents. + // + // THE ROOT CAUSE (Mongoose Schema Behavior): + // ------------------------------------------- + // In app/models/subschemas/attack-pattern.js, these fields are defined as: + // x_mitre_platforms: [String] + // x_mitre_contributors: [String] + // + // When a field is defined this way in Mongoose (without `default: undefined`), + // Mongoose will: + // 1. Initialize the field as an empty array [] when the document is created + // (even if not provided in the request) + // 2. Serialize the field as an empty array [] when returning the document + // + // This causes a problem in our tests: + // - POST request WITHOUT these fields → Mongoose stores them as [] + // - POST response → Server returns { x_mitre_platforms: [], x_mitre_contributors: [] } + // - PUT request spreads the response → Sends empty arrays back to server + // - ADM validation FAILS because the schemas require: + // x_mitre_platforms: z.array(...).min(1, 'At least one platform is required').optional() + // x_mitre_contributors: z.array(...).nonempty().optional() + // - Empty arrays [] violate the .min(1) and .nonempty() constraints + // + // ADM SCHEMA VALIDATION RULES (Conditionally Required Fields): + // ------------------------------------------------------------- + // These fields are "conditionally required" - optional to include, but IF + // included must meet constraints: + // - If omitted entirely (key not present): ✓ VALID (field is optional) + // - If present with empty array []: ✗ INVALID (violates .min(1) / .nonempty()) + // - If present with valid items: ✓ VALID + // + // WHY WE POPULATE THEM HERE: + // -------------------------- + // By populating these fields with valid values BEFORE the initial POST request: + // 1. POST request includes valid arrays: ['Windows'], ['Test Contributor'] + // 2. Mongoose stores them with valid data (not empty arrays) + // 3. POST/GET responses return valid arrays + // 4. PUT requests spread valid arrays (not empty ones) + // 5. Validation passes throughout the entire POST → GET → PUT cycle + // + // FUTURE FIX (Recommended for separate PR): + // ------------------------------------------ + // The proper architectural fix is to update all Mongoose schemas to use: + // x_mitre_platforms: { type: [String], default: undefined } + // x_mitre_contributors: { type: [String], default: undefined } + // + // This would prevent Mongoose from initializing/serializing these fields when + // not provided, matching user expectations and avoiding unexpected behavior. + // + // NOTE: This issue affects other array fields in the schemas (external_references, + // object_marking_refs, and various workspace fields) and should be addressed + // comprehensively in a future PR to avoid scope creep. + // ============================================================================= + + if (!syntheticStix.x_mitre_platforms || syntheticStix.x_mitre_platforms.length === 0) { + syntheticStix.x_mitre_platforms = ['Windows']; + } + if (!syntheticStix.x_mitre_contributors || syntheticStix.x_mitre_contributors.length === 0) { + syntheticStix.x_mitre_contributors = ['Test Contributor']; + } + + return syntheticStix; + } + + before(async function () { + // Enable ADM validation and disable OpenAPI validation + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = false; + + // Establish the database connection + await database.initializeConnection(); + + // Check for a valid database configuration + await databaseConfiguration.checkSystemConfiguration(); + + // Initialize the express app + app = await require('../../../index').initializeApp(); + + // Log into the app + passportCookie = await login.loginAnonymous(app); + }); + + after(async function () { + // Restore default config values + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + }); + + describe('POST operations - work-in-progress (partial validation)', function () { + it('should accept valid complete data in work-in-progress state', async function () { + const syntheticStix = createSyntheticStix(stixType); + + const requestBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(201); + expect(res.body).toBeDefined(); + expect(res.body.stix).toBeDefined(); + expect(res.body.stix.type).toBe(stixType); + }); + + it('should accept partial data in work-in-progress state (missing optional fields)', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Remove optional fields to test partial validation + delete syntheticStix.description; + delete syntheticStix.x_mitre_platforms; + delete syntheticStix.x_mitre_data_sources; + + const requestBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + // Should succeed because work-in-progress uses partial validation + expect(res.status).toBe(201); + expect(res.body.stix.type).toBe(stixType); + }); + + it('should reject data with invalid field values in work-in-progress state', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Make a field invalid + syntheticStix.x_mitre_is_subtechnique = 'not-a-boolean'; // Should be boolean + + const requestBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + // Should fail validation + expect(res.status).toBe(400); + expect(res.body.error).toBeDefined(); + expect(res.body.details).toBeDefined(); + expect(Array.isArray(res.body.details)).toBe(true); + }); + }); + + describe('POST operations - reviewed (full validation)', function () { + it('should accept valid complete data in reviewed state', async function () { + const syntheticStix = createSyntheticStix(stixType); + + const requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(201); + expect(res.body).toBeDefined(); + expect(res.body.stix).toBeDefined(); + expect(res.body.stix.type).toBe(stixType); + }); + + it('should reject data missing required fields in reviewed state', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Remove a required field + delete syntheticStix.name; + + const requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + // Should fail validation because 'name' is required in full validation + expect(res.status).toBe(400); + expect(res.body.error).toBeDefined(); + expect(res.body.details).toBeDefined(); + expect(Array.isArray(res.body.details)).toBe(true); + }); + + it('should reject data with invalid field values in reviewed state', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Make a field invalid + syntheticStix.type = 'invalid-type'; // Wrong type + + const requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + // Should fail validation + expect(res.status).toBe(400); + expect(res.body.error).toBeDefined(); + expect(res.body.details).toBeDefined(); + }); + }); + + describe('PUT operations - work-in-progress (partial validation)', function () { + let createdObject; + + beforeEach(async function () { + // Create an object to update + const syntheticStix = createSyntheticStix(stixType); + + const createBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + const createRes = await request(app) + .post(endpoint) + .send(createBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + createdObject = createRes.body; + }); + + it('should accept valid updates in work-in-progress state', async function () { + const updateBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + ...createdObject.stix, + name: 'Updated Technique Name', + description: 'Updated description', + }, + }; + + // Remove server-managed field (server adds this automatically) + delete updateBody.stix.x_mitre_attack_spec_version; + // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + + const res = await request(app) + .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + if (res.status !== 200) { + logger.debug('=== REQUEST FAILED ==='); + logger.debug('Status:', res.status); + logger.debug('Errors:', JSON.stringify(res.body, null, 2)); + } + + expect(res.status).toBe(200); + expect(res.body.stix.name).toBe('Updated Technique Name'); + }); + + it('should accept updates with missing optional fields in work-in-progress state', async function () { + const updateBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + ...createdObject.stix, + name: 'Updated Name', + }, + }; + + // Remove optional fields to test partial validation + delete updateBody.stix.description; + delete updateBody.stix.x_mitre_platforms; + + // Remove server-managed field + delete updateBody.stix.x_mitre_attack_spec_version; + // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + + const res = await request(app) + .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(200); + }); + + it('should reject updates with invalid field values in work-in-progress state', async function () { + const updateBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + ...createdObject.stix, + x_mitre_is_subtechnique: 'not-a-boolean', // Invalid type + }, + }; + + // Remove server-managed field + delete updateBody.stix.x_mitre_attack_spec_version; + // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + + const res = await request(app) + .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(400); + expect(res.body.error).toBeDefined(); + }); + }); + + describe('PUT operations - reviewed (full validation)', function () { + let createdObject; + + beforeEach(async function () { + // Create an object to update + const syntheticStix = createSyntheticStix(stixType); + + const createBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + const createRes = await request(app) + .post(endpoint) + .send(createBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + createdObject = createRes.body; + }); + + it('should accept valid complete updates in reviewed state', async function () { + const updateBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: { + ...createdObject.stix, + name: 'Reviewed Technique Name', + }, + }; + + // Remove server-managed field + delete updateBody.stix.x_mitre_attack_spec_version; + // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + + const res = await request(app) + .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(200); + expect(res.body.stix.name).toBe('Reviewed Technique Name'); + }); + + it('should reject updates missing required fields in reviewed state', async function () { + const updateBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: { + ...createdObject.stix, + }, + }; + + // Remove required field + delete updateBody.stix.name; + // Remove server-managed field + delete updateBody.stix.x_mitre_attack_spec_version; + // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + + const res = await request(app) + .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(400); + expect(res.body.error).toBeDefined(); + }); + }); + + describe('Validation toggle', function () { + it('should skip validation when ADM validation is disabled', async function () { + // Temporarily disable ADM validation + config.validateRequests.withAttackDataModel = false; + + const syntheticStix = createSyntheticStix(stixType); + + // Remove a required field - this would normally fail validation + delete syntheticStix.name; + + const requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + + // Should NOT return 400 with "Invalid data" error because ADM validation is disabled + // The request will likely fail at the database level (missing required field), + // but it should NOT fail with ADM validation error + if (res.status === 400 && res.headers['content-type']?.includes('json')) { + expect(res.body.error).not.toBe('Invalid data'); + } + + // Re-enable ADM validation + config.validateRequests.withAttackDataModel = true; + }); + + it('should enforce validation when ADM validation is enabled', async function () { + // Ensure ADM validation is enabled + config.validateRequests.withAttackDataModel = true; + + const syntheticStix = createSyntheticStix(stixType); + + // Remove required field + delete syntheticStix.name; + + const requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + // Should return 400 with validation error + expect(res.status).toBe(400); + expect(res.body.error).toBe('Invalid data'); + expect(res.body.details).toBeDefined(); + }); + }); + + describe('Error response format', function () { + it('should return detailed validation errors with proper structure', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Create multiple validation errors + delete syntheticStix.name; // Missing required field + syntheticStix.x_mitre_is_subtechnique = 'invalid'; // Wrong type + + const requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect('Content-Type', /json/); + + expect(res.status).toBe(400); + expect(res.body.error).toBe('Invalid data'); + expect(res.body.details).toBeDefined(); + expect(Array.isArray(res.body.details)).toBe(true); + expect(res.body.details.length).toBeGreaterThan(0); + + // Verify each error has expected structure + res.body.details.forEach((detail) => { + expect(detail).toHaveProperty('message'); + expect(detail).toHaveProperty('path'); + }); + }); + }); +}); From d95a6fa8b3afc41cab6d75f4343edc32adcbb8ec Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:18:25 -0400 Subject: [PATCH 028/370] test: explicitly set config.validateRequests runtime parameters in all spec.js files - Currently, the tests are not interoperable with the new ADM validation middleware - Though the ADM validation middleware doesn't change backend workflows, the tests use mock data - This mock data is mostly non-compliant with STIX with makes erroneous assumptions about required vs optional fields - Until we refactor the tests to use a more robust mock data generator, we continue using the OpenAPI spec validators - Explicitly set the config.validateRequests runtime params to signal this --- app/tests/api/analytics/analytics-includeRefs.spec.js | 9 +++++++++ app/tests/api/assets/assets.spec.js | 4 ++++ app/tests/api/attack-objects/attack-objects.spec.js | 5 +++++ app/tests/api/campaigns/campaigns.spec.js | 4 ++++ .../api/collection-bundles/collection-bundles.spec.js | 4 ++++ .../api/collection-indexes/collection-indexes.spec.js | 5 +++++ app/tests/api/collections/collections.spec.js | 4 ++++ app/tests/api/data-components/data-components.spec.js | 4 ++++ app/tests/api/data-sources/data-sources.spec.js | 4 ++++ .../detection-strategies/detection-strategies-spec.js | 4 ++++ app/tests/api/groups/groups-input-validation.spec.js | 5 +++++ app/tests/api/groups/groups.query.spec.js | 5 +++++ app/tests/api/groups/groups.spec.js | 4 ++++ app/tests/api/identities/identities.spec.js | 4 ++++ .../api/marking-definitions/marking-definitions.spec.js | 6 +++++- app/tests/api/matrices/matrices.spec.js | 4 ++++ app/tests/api/mitigations/mitigations.spec.js | 4 ++++ app/tests/api/notes/notes.spec.js | 4 ++++ app/tests/api/recent-activity/recent-activity.spec.js | 6 +++++- app/tests/api/references/references.spec.js | 6 +++++- app/tests/api/relationships/relationships.spec.js | 4 ++++ app/tests/api/session/session.spec.js | 6 +++++- app/tests/api/software/software.spec.js | 4 ++++ app/tests/api/stix-bundles/stix-bundles.spec.js | 5 +++++ .../system-configuration/create-object-identity.spec.js | 6 +++++- .../system-configuration/system-configuration.spec.js | 6 +++++- app/tests/api/tactics/tactics.spec.js | 4 ++++ app/tests/api/tactics/tactics.techniques.spec.js | 6 +++++- app/tests/api/teams/teams-invalid.spec.js | 5 +++++ app/tests/api/teams/teams.spec.js | 5 +++++ app/tests/api/techniques/techniques.query.spec.js | 5 +++++ app/tests/api/techniques/techniques.spec.js | 4 ++++ app/tests/api/techniques/techniques.tactics.spec.js | 6 +++++- .../api/user-accounts/user-accounts-invalid.spec.js | 5 +++++ app/tests/api/user-accounts/user-accounts.spec.js | 5 +++++ 35 files changed, 163 insertions(+), 8 deletions(-) diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index 5d5262d6..61bafd5d 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -4,11 +4,16 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); logger.level = 'debug'; +const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/dist/generator'); + +createSyntheticStixObject(); + // Test data for analytics with data component references const analyticData = { workspace: { @@ -103,6 +108,10 @@ describe('Analytics API - includeRefs Parameter', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/assets/assets.spec.js b/app/tests/api/assets/assets.spec.js index 478b44b1..53360dc0 100644 --- a/app/tests/api/assets/assets.spec.js +++ b/app/tests/api/assets/assets.spec.js @@ -49,6 +49,10 @@ describe('Assets API', function () { let passportCookie; before(async function () { + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Establish the database connection // Use an in-memory database that we spin up for the test await database.initializeConnection(); diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index 5c9ff8da..00c6ab1e 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -6,6 +6,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const AttackObject = require('../../../models/attack-object-model'); +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); @@ -55,6 +56,10 @@ describe('ATT&CK Objects API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/campaigns/campaigns.spec.js b/app/tests/api/campaigns/campaigns.spec.js index c70970c4..4413e762 100644 --- a/app/tests/api/campaigns/campaigns.spec.js +++ b/app/tests/api/campaigns/campaigns.spec.js @@ -85,6 +85,10 @@ describe('Campaigns API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/collection-bundles/collection-bundles.spec.js b/app/tests/api/collection-bundles/collection-bundles.spec.js index 40d5aade..801f28bf 100644 --- a/app/tests/api/collection-bundles/collection-bundles.spec.js +++ b/app/tests/api/collection-bundles/collection-bundles.spec.js @@ -442,6 +442,10 @@ describe('Collection Bundles Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/collection-indexes/collection-indexes.spec.js b/app/tests/api/collection-indexes/collection-indexes.spec.js index 28cbb2bd..d200f5de 100644 --- a/app/tests/api/collection-indexes/collection-indexes.spec.js +++ b/app/tests/api/collection-indexes/collection-indexes.spec.js @@ -4,6 +4,7 @@ const { expect } = require('expect'); const logger = require('../../../lib/logger'); logger.level = 'debug'; +const config = require('../../../config/config'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const login = require('../../shared/login'); @@ -73,6 +74,10 @@ describe('Collection Indexes Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/collections/collections.spec.js b/app/tests/api/collections/collections.spec.js index 0e9e4e60..684e984c 100644 --- a/app/tests/api/collections/collections.spec.js +++ b/app/tests/api/collections/collections.spec.js @@ -134,6 +134,10 @@ describe('Collections (x-mitre-collection) Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index be3643c9..ec51f841 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -55,6 +55,10 @@ describe('Data Components API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index e2a07c00..183bd6a5 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -103,6 +103,10 @@ describe('Data Sources API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index a873e902..77a86f96 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -54,6 +54,10 @@ describe('Detection Strategies API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/groups/groups-input-validation.spec.js b/app/tests/api/groups/groups-input-validation.spec.js index ba2c73ca..6ae8745e 100644 --- a/app/tests/api/groups/groups-input-validation.spec.js +++ b/app/tests/api/groups/groups-input-validation.spec.js @@ -4,6 +4,7 @@ const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const Group = require('../../../models/group-model'); +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); @@ -93,6 +94,10 @@ describe('Groups API Input Validation', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 7819a2e6..0dbb339e 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -5,6 +5,7 @@ const { expect } = require('expect'); const _ = require('lodash'); const uuid = require('uuid'); +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); @@ -165,6 +166,10 @@ describe('Groups API Queries', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/groups/groups.spec.js b/app/tests/api/groups/groups.spec.js index 515084b7..1ff5b2a2 100644 --- a/app/tests/api/groups/groups.spec.js +++ b/app/tests/api/groups/groups.spec.js @@ -80,6 +80,10 @@ describe('Groups API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/identities/identities.spec.js b/app/tests/api/identities/identities.spec.js index ad9c9f3e..db738428 100644 --- a/app/tests/api/identities/identities.spec.js +++ b/app/tests/api/identities/identities.spec.js @@ -41,6 +41,10 @@ describe('Identity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/marking-definitions/marking-definitions.spec.js b/app/tests/api/marking-definitions/marking-definitions.spec.js index d0b5707a..c8f104e5 100644 --- a/app/tests/api/marking-definitions/marking-definitions.spec.js +++ b/app/tests/api/marking-definitions/marking-definitions.spec.js @@ -3,7 +3,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); - +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); @@ -38,6 +38,10 @@ describe('Marking Definitions API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/matrices/matrices.spec.js b/app/tests/api/matrices/matrices.spec.js index 01ea2922..e6c66ccb 100644 --- a/app/tests/api/matrices/matrices.spec.js +++ b/app/tests/api/matrices/matrices.spec.js @@ -60,6 +60,10 @@ describe('Matrices API', function () { // Initialize the express app app = await require('../../../index').initializeApp(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Log into the app passportCookie = await login.loginAnonymous(app); }); diff --git a/app/tests/api/mitigations/mitigations.spec.js b/app/tests/api/mitigations/mitigations.spec.js index ca5894c5..d7643915 100644 --- a/app/tests/api/mitigations/mitigations.spec.js +++ b/app/tests/api/mitigations/mitigations.spec.js @@ -44,6 +44,10 @@ describe('Mitigations API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/notes/notes.spec.js b/app/tests/api/notes/notes.spec.js index b47ed430..7f05b6e4 100644 --- a/app/tests/api/notes/notes.spec.js +++ b/app/tests/api/notes/notes.spec.js @@ -44,6 +44,10 @@ describe('Notes API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/recent-activity/recent-activity.spec.js b/app/tests/api/recent-activity/recent-activity.spec.js index e7ec1073..6c5b6f39 100644 --- a/app/tests/api/recent-activity/recent-activity.spec.js +++ b/app/tests/api/recent-activity/recent-activity.spec.js @@ -5,7 +5,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); - +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); @@ -30,6 +30,10 @@ describe('Recent Activity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/references/references.spec.js b/app/tests/api/references/references.spec.js index 32aedffe..9b597aa7 100644 --- a/app/tests/api/references/references.spec.js +++ b/app/tests/api/references/references.spec.js @@ -4,7 +4,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const login = require('../../shared/login'); - +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -45,6 +45,10 @@ describe('References API', function () { // Wait until the Reference indexes are created await Reference.init(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 06f6536e..3c0edf13 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -53,6 +53,10 @@ describe('Relationships API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/session/session.spec.js b/app/tests/api/session/session.spec.js index b94d3461..01a05b60 100644 --- a/app/tests/api/session/session.spec.js +++ b/app/tests/api/session/session.spec.js @@ -3,7 +3,7 @@ const request = require('supertest'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); - +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -18,6 +18,10 @@ describe('Session API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); }); diff --git a/app/tests/api/software/software.spec.js b/app/tests/api/software/software.spec.js index 5052b621..499d9f21 100644 --- a/app/tests/api/software/software.spec.js +++ b/app/tests/api/software/software.spec.js @@ -60,6 +60,10 @@ describe('Software API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/stix-bundles/stix-bundles.spec.js b/app/tests/api/stix-bundles/stix-bundles.spec.js index f58d69cc..ec6df8a3 100644 --- a/app/tests/api/stix-bundles/stix-bundles.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles.spec.js @@ -1,6 +1,7 @@ const request = require('supertest'); const { expect } = require('expect'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -670,6 +671,10 @@ describe('STIX Bundles Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/system-configuration/create-object-identity.spec.js b/app/tests/api/system-configuration/create-object-identity.spec.js index 0bdbc2b3..cd2475ab 100644 --- a/app/tests/api/system-configuration/create-object-identity.spec.js +++ b/app/tests/api/system-configuration/create-object-identity.spec.js @@ -6,7 +6,7 @@ const uuid = require('uuid'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const login = require('../../shared/login'); - +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -54,6 +54,10 @@ describe('Create Object with Organization Identity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/system-configuration/system-configuration.spec.js b/app/tests/api/system-configuration/system-configuration.spec.js index 17279e35..1d443d5e 100644 --- a/app/tests/api/system-configuration/system-configuration.spec.js +++ b/app/tests/api/system-configuration/system-configuration.spec.js @@ -4,7 +4,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const login = require('../../shared/login'); - +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -37,6 +37,10 @@ describe('System Configuration API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index 7097e94a..3fecbff6 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -41,6 +41,10 @@ describe('Tactics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index 2fc21bfb..f8e3f0ae 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -4,7 +4,7 @@ const request = require('supertest'); const { expect } = require('expect'); const login = require('../../shared/login'); - +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -30,6 +30,10 @@ describe('Tactics with Techniques API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/teams/teams-invalid.spec.js b/app/tests/api/teams/teams-invalid.spec.js index ae1c6e13..01935082 100644 --- a/app/tests/api/teams/teams-invalid.spec.js +++ b/app/tests/api/teams/teams-invalid.spec.js @@ -1,5 +1,6 @@ const request = require('supertest'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -22,6 +23,10 @@ describe('Teams API Test Invalid Data', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/teams/teams.spec.js b/app/tests/api/teams/teams.spec.js index 3a60c8f0..1f06726b 100644 --- a/app/tests/api/teams/teams.spec.js +++ b/app/tests/api/teams/teams.spec.js @@ -1,6 +1,7 @@ const request = require('supertest'); const { expect } = require('expect'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -50,6 +51,10 @@ describe('Teams API', function () { const user1 = new UserAccount(exampleUser); await user1.save(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.query.spec.js b/app/tests/api/techniques/techniques.query.spec.js index 340af8be..a9faa590 100644 --- a/app/tests/api/techniques/techniques.query.spec.js +++ b/app/tests/api/techniques/techniques.query.spec.js @@ -5,6 +5,7 @@ const { expect } = require('expect'); const _ = require('lodash'); const uuid = require('uuid'); +const config = require('../../../config/config'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); @@ -138,6 +139,10 @@ describe('Techniques Query API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.spec.js b/app/tests/api/techniques/techniques.spec.js index 743a19b7..233f9971 100644 --- a/app/tests/api/techniques/techniques.spec.js +++ b/app/tests/api/techniques/techniques.spec.js @@ -57,6 +57,10 @@ describe('Techniques Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.tactics.spec.js b/app/tests/api/techniques/techniques.tactics.spec.js index 055a0bef..44bf10ce 100644 --- a/app/tests/api/techniques/techniques.tactics.spec.js +++ b/app/tests/api/techniques/techniques.tactics.spec.js @@ -4,7 +4,7 @@ const request = require('supertest'); const { expect } = require('expect'); const login = require('../../shared/login'); - +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -30,6 +30,10 @@ describe('Techniques with Tactics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/user-accounts/user-accounts-invalid.spec.js b/app/tests/api/user-accounts/user-accounts-invalid.spec.js index be21b482..ccec4f9e 100644 --- a/app/tests/api/user-accounts/user-accounts-invalid.spec.js +++ b/app/tests/api/user-accounts/user-accounts-invalid.spec.js @@ -1,5 +1,6 @@ const request = require('supertest'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -22,6 +23,10 @@ describe('User Accounts API Test Invalid Data', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index f5b4257f..c931c5ff 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -1,6 +1,7 @@ const request = require('supertest'); const { expect } = require('expect'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -37,6 +38,10 @@ describe('User Accounts API', function () { await UserAccount.init(); await Team.init(); + // Disable validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); From 0324ff37b184327efb6ea1525d93cc32f10d15e0 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:19:53 -0400 Subject: [PATCH 029/370] fix(openapi): restore requestBody validation with YAML-based spec files --- app/api/definitions/paths/analytics-paths.yml | 8 ++++---- app/api/definitions/paths/assets-paths.yml | 8 ++++---- app/api/definitions/paths/campaigns-paths.yml | 8 ++++---- app/api/definitions/paths/collections-paths.yml | 4 ++-- app/api/definitions/paths/data-components-paths.yml | 8 ++++---- app/api/definitions/paths/data-sources-paths.yml | 8 ++++---- app/api/definitions/paths/detection-strategies-paths.yml | 8 ++++---- app/api/definitions/paths/groups-paths.yml | 8 ++++---- app/api/definitions/paths/identities-paths.yml | 8 ++++---- app/api/definitions/paths/matrices-paths.yml | 8 ++++---- app/api/definitions/paths/mitigations-paths.yml | 8 ++++---- app/api/definitions/paths/tactics-paths.yml | 8 ++++---- app/api/definitions/paths/techniques-paths.yml | 8 ++++---- 13 files changed, 50 insertions(+), 50 deletions(-) diff --git a/app/api/definitions/paths/analytics-paths.yml b/app/api/definitions/paths/analytics-paths.yml index 5d8a355d..c1d51b96 100644 --- a/app/api/definitions/paths/analytics-paths.yml +++ b/app/api/definitions/paths/analytics-paths.yml @@ -124,8 +124,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/analytics.yml#/components/schemas/analytic' # TODO delete after ADM integration complete + # type: object + $ref: '../components/analytics.yml#/components/schemas/analytic' # TODO delete after ADM integration complete responses: '201': description: 'The analytic has been successfully created.' @@ -259,8 +259,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/analytics.yml#/components/schemas/analytic' # TODO delete after ADM integration complete + # type: object + $ref: '../components/analytics.yml#/components/schemas/analytic' # TODO delete after ADM integration complete responses: '200': description: 'The analytic was updated.' diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index a27cdc29..ed98919c 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -128,8 +128,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/assets.yml#/components/schemas/asset' + # type: object + $ref: '../components/assets.yml#/components/schemas/asset' # TODO delete after ADM integration complete responses: '201': description: 'The asset has been successfully created.' @@ -253,8 +253,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/assets.yml#/components/schemas/asset' # TODO delete after ADM integration complete + # type: object + $ref: '../components/assets.yml#/components/schemas/asset' # TODO delete after ADM integration complete responses: '200': description: 'The asset was updated.' diff --git a/app/api/definitions/paths/campaigns-paths.yml b/app/api/definitions/paths/campaigns-paths.yml index eb3f230f..f4a78d69 100644 --- a/app/api/definitions/paths/campaigns-paths.yml +++ b/app/api/definitions/paths/campaigns-paths.yml @@ -104,8 +104,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/campaigns.yml#/components/schemas/campaign' # TODO delete after ADM integration complete + # type: object + $ref: '../components/campaigns.yml#/components/schemas/campaign' # TODO delete after ADM integration complete responses: '201': description: 'The campaign has been successfully created.' @@ -231,8 +231,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/campaigns.yml#/components/schemas/campaign' # TODO delete after ADM integration complete + # type: object + $ref: '../components/campaigns.yml#/components/schemas/campaign' # TODO delete after ADM integration complete responses: '200': description: 'The campaign was updated.' diff --git a/app/api/definitions/paths/collections-paths.yml b/app/api/definitions/paths/collections-paths.yml index d69602d7..f2bcb30f 100644 --- a/app/api/definitions/paths/collections-paths.yml +++ b/app/api/definitions/paths/collections-paths.yml @@ -107,8 +107,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/collections.yml#/components/schemas/collection' # TODO delete after ADM integration complete + # type: object + $ref: '../components/collections.yml#/components/schemas/collection' # TODO delete after ADM integration complete responses: '201': description: 'The collection has been successfully created.' diff --git a/app/api/definitions/paths/data-components-paths.yml b/app/api/definitions/paths/data-components-paths.yml index b29b6dab..50310db1 100644 --- a/app/api/definitions/paths/data-components-paths.yml +++ b/app/api/definitions/paths/data-components-paths.yml @@ -104,8 +104,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/data-components.yml#/components/schemas/data-component' # TODO delete after ADM integration complete + # type: object + $ref: '../components/data-components.yml#/components/schemas/data-component' # TODO delete after ADM integration complete responses: '201': description: 'The data component has been successfully created.' @@ -285,8 +285,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/data-components.yml#/components/schemas/data-component' # TODO delete after ADM integration complete + # type: object + $ref: '../components/data-components.yml#/components/schemas/data-component' # TODO delete after ADM integration complete responses: '200': description: 'The data component was updated.' diff --git a/app/api/definitions/paths/data-sources-paths.yml b/app/api/definitions/paths/data-sources-paths.yml index 3cc2cf84..40ac7b67 100644 --- a/app/api/definitions/paths/data-sources-paths.yml +++ b/app/api/definitions/paths/data-sources-paths.yml @@ -128,8 +128,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/data-sources.yml#/components/schemas/data-source' # TODO delete after ADM integration complete + # type: object + $ref: '../components/data-sources.yml#/components/schemas/data-source' # TODO delete after ADM integration complete responses: '201': description: 'The data source has been successfully created.' @@ -269,8 +269,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/data-sources.yml#/components/schemas/data-source' # TODO delete after ADM integration complete + # type: object + $ref: '../components/data-sources.yml#/components/schemas/data-source' # TODO delete after ADM integration complete responses: '200': description: 'The data source was updated.' diff --git a/app/api/definitions/paths/detection-strategies-paths.yml b/app/api/definitions/paths/detection-strategies-paths.yml index 00466053..6d430148 100644 --- a/app/api/definitions/paths/detection-strategies-paths.yml +++ b/app/api/definitions/paths/detection-strategies-paths.yml @@ -116,8 +116,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' # TODO delete after ADM integration complete + # type: object + $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' # TODO delete after ADM integration complete responses: '201': description: 'The detection strategy has been successfully created.' @@ -243,8 +243,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' # TODO delete after ADM integration complete + # type: object + $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' # TODO delete after ADM integration complete responses: '200': description: 'The detection strategy was updated.' diff --git a/app/api/definitions/paths/groups-paths.yml b/app/api/definitions/paths/groups-paths.yml index 3862bc5f..cfbf7920 100644 --- a/app/api/definitions/paths/groups-paths.yml +++ b/app/api/definitions/paths/groups-paths.yml @@ -104,8 +104,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/groups.yml#/components/schemas/group' # TODO delete after ADM integration complete + # type: object + $ref: '../components/groups.yml#/components/schemas/group' # TODO delete after ADM integration complete responses: '201': description: 'The group has been successfully created.' @@ -231,8 +231,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/groups.yml#/components/schemas/group' # TODO delete after ADM integration complete + # type: object + $ref: '../components/groups.yml#/components/schemas/group' # TODO delete after ADM integration complete responses: '200': description: 'The group was updated.' diff --git a/app/api/definitions/paths/identities-paths.yml b/app/api/definitions/paths/identities-paths.yml index dc0d8781..b0a8c7d4 100644 --- a/app/api/definitions/paths/identities-paths.yml +++ b/app/api/definitions/paths/identities-paths.yml @@ -85,8 +85,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/identities.yml#/components/schemas/identity' # TODO delete after ADM integration complete + # type: object + $ref: '../components/identities.yml#/components/schemas/identity' # TODO delete after ADM integration complete responses: '201': description: 'The identity has been successfully created.' @@ -210,8 +210,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/identities.yml#/components/schemas/identity' # TODO delete after ADM integration complete + # type: object + $ref: '../components/identities.yml#/components/schemas/identity' # TODO delete after ADM integration complete responses: '200': description: 'The identity was updated.' diff --git a/app/api/definitions/paths/matrices-paths.yml b/app/api/definitions/paths/matrices-paths.yml index af37c583..e9ae088e 100644 --- a/app/api/definitions/paths/matrices-paths.yml +++ b/app/api/definitions/paths/matrices-paths.yml @@ -104,8 +104,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/matrices.yml#/components/schemas/matrix' # TODO delete after ADM integration complete + # type: object + $ref: '../components/matrices.yml#/components/schemas/matrix' # TODO delete after ADM integration complete responses: '201': description: 'The matrix has been successfully created.' @@ -231,8 +231,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/matrices.yml#/components/schemas/matrix' # TODO delete after ADM integration complete + # type: object + $ref: '../components/matrices.yml#/components/schemas/matrix' # TODO delete after ADM integration complete responses: '200': description: 'The matrix was updated.' diff --git a/app/api/definitions/paths/mitigations-paths.yml b/app/api/definitions/paths/mitigations-paths.yml index 632dc1a9..ed5aa1d7 100644 --- a/app/api/definitions/paths/mitigations-paths.yml +++ b/app/api/definitions/paths/mitigations-paths.yml @@ -116,8 +116,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/mitigations.yml#/components/schemas/mitigation' # TODO delete after ADM integration complete + # type: object + $ref: '../components/mitigations.yml#/components/schemas/mitigation' # TODO delete after ADM integration complete responses: '201': description: 'The mitigation has been successfully created.' @@ -243,8 +243,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/mitigations.yml#/components/schemas/mitigation' # TODO delete after ADM integration complete + # type: object + $ref: '../components/mitigations.yml#/components/schemas/mitigation' # TODO delete after ADM integration complete responses: '200': description: 'The mitigation was updated.' diff --git a/app/api/definitions/paths/tactics-paths.yml b/app/api/definitions/paths/tactics-paths.yml index 59dd8985..212785e9 100644 --- a/app/api/definitions/paths/tactics-paths.yml +++ b/app/api/definitions/paths/tactics-paths.yml @@ -116,8 +116,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/tactics.yml#/components/schemas/tactic' # TODO delete after ADM integration complete + # type: object + $ref: '../components/tactics.yml#/components/schemas/tactic' # TODO delete after ADM integration complete responses: '201': description: 'The tactic has been successfully created.' @@ -243,8 +243,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/tactics.yml#/components/schemas/tactic' # TODO delete after ADM integration complete + # type: object + $ref: '../components/tactics.yml#/components/schemas/tactic' # TODO delete after ADM integration complete responses: '200': description: 'The tactic was updated.' diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index 82466eaa..92435c90 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -129,8 +129,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/techniques.yml#/components/schemas/technique' + # type: object + $ref: '../components/techniques.yml#/components/schemas/technique' # TODO delete after ADM integration complete responses: '201': description: 'The technique has been successfully created.' @@ -256,8 +256,8 @@ paths: content: application/json: schema: - type: object - # $ref: '../components/techniques.yml#/components/schemas/technique' # TODO delete after ADM integration complete + # type: object + $ref: '../components/techniques.yml#/components/schemas/technique' # TODO delete after ADM integration complete responses: '200': description: 'The technique was updated.' From d76de47ec77538d03b19bed41802ab7dc2fbf8eb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:45:21 -0400 Subject: [PATCH 030/370] test: move adm validation middleware to new dedicated middleware package --- .../adm-validation-middleware.spec.js} | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename app/tests/{api/validation/adm-validation.spec.js => middleware/adm-validation-middleware.spec.js} (98%) diff --git a/app/tests/api/validation/adm-validation.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js similarity index 98% rename from app/tests/api/validation/adm-validation.spec.js rename to app/tests/middleware/adm-validation-middleware.spec.js index 35691c91..cb4b868c 100644 --- a/app/tests/api/validation/adm-validation.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const database = require('../../../lib/database-in-memory'); -const databaseConfiguration = require('../../../lib/database-configuration'); -const config = require('../../../config/config'); -const login = require('../../shared/login'); +const database = require('../../lib/database-in-memory'); +const databaseConfiguration = require('../../lib/database-configuration'); +const config = require('../../config/config'); +const login = require('../shared/login'); -const logger = require('../../../lib/logger'); +const logger = require('../../lib/logger'); logger.level = 'debug'; const uuid = require('uuid'); @@ -29,7 +29,7 @@ const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/d * * NOTE: Tests focus on techniques initially. Once validated, can be generalized to other types. */ -describe('ADM Validation Middleware (Smoke Tests)', function () { +describe('ADM Validation Middleware', function () { let app; let passportCookie; @@ -144,7 +144,7 @@ describe('ADM Validation Middleware (Smoke Tests)', function () { await databaseConfiguration.checkSystemConfiguration(); // Initialize the express app - app = await require('../../../index').initializeApp(); + app = await require('../../index').initializeApp(); // Log into the app passportCookie = await login.loginAnonymous(app); From 91e31b8cfe43d4ee7a1794bae05d323c3e2d0011 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:45:59 -0400 Subject: [PATCH 031/370] build(npm): add test:middleware script and bump test timeouts to 20s --- package.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d8336af6..36979b2e 100644 --- a/package.json +++ b/package.json @@ -27,13 +27,14 @@ "format": "npm run prettier:fix && npm run lint:fix", "snyk": "snyk test --insecure --fail-on=upgradable", "start": "node ./bin/www", - "test": "npm run test:openapi && npm run test:config && npm run test:api", - "test:api": "mocha --timeout 10000 --recursive ./app/tests/api", - "test:config": "mocha --timeout 10000 --recursive ./app/tests/config", - "test:import": "mocha --timeout 10000 --recursive ./app/tests/import", - "test:openapi": "mocha --timeout 10000 ./app/tests/openapi", + "test": "npm run test:openapi && npm run test:config && npm run test:api && npm run test:middleware", + "test:api": "mocha --timeout 20000 --recursive ./app/tests/api", + "test:config": "mocha --timeout 20000 --recursive ./app/tests/config", + "test:import": "mocha --timeout 20000 --recursive ./app/tests/import", + "test:openapi": "mocha --timeout 20000 ./app/tests/openapi", + "test:middleware": "mocha --timeout 20000 ./app/tests/middleware", "test:authn": "./app/tests/run-mocha-separate-jobs.sh ./app/tests/authn", - "test:fuzz": "mocha --timeout 10000 --recursive ./app/tests/fuzz", + "test:fuzz": "mocha --timeout 20000 --recursive ./app/tests/fuzz", "test:scheduler": "mocha --timeout 60000 --recursive ./app/tests/scheduler" }, "dependencies": { From 63d46b159dc831c2a8e5f6ed0d256afe16cd578b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:48:52 -0400 Subject: [PATCH 032/370] feat(logger): revert original logger format for info log level - When LOG_LEVEL is info, the server will output succinct one-line log statements - When LOG_LEVEL is NOT info, the server will output verbose, multi-line JSON log statements --- app/lib/logger.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/lib/logger.js b/app/lib/logger.js index e4bcdacf..10732166 100644 --- a/app/lib/logger.js +++ b/app/lib/logger.js @@ -15,13 +15,23 @@ const config = require('../config/config'); // } // } -const consoleFormat = winston.format.combine( - winston.format.timestamp(), - winston.format.printf( - (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, - ), - // winston.format.printf(info => `${ info.timestamp } [${ info.level.toUpperCase() }] ${ formatId(info) }${ info.message }`) -); +let consoleFormat; +if (config.logging.logLevel === 'info') { + consoleFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.printf( + (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, + ), + // winston.format.printf(info => `${ info.timestamp } [${ info.level.toUpperCase() }] ${ formatId(info) }${ info.message }`) + ); +} else { + consoleFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.prettyPrint(), + // winston.format.simple(), + ); +} const logLevels = { error: 0, From 22672e70a385f18a3c57ea7c8c4c0a7707b779a7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:12:17 -0400 Subject: [PATCH 033/370] build(npm): add --exit flag to test:middleware script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36979b2e..a2e19616 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "test:config": "mocha --timeout 20000 --recursive ./app/tests/config", "test:import": "mocha --timeout 20000 --recursive ./app/tests/import", "test:openapi": "mocha --timeout 20000 ./app/tests/openapi", - "test:middleware": "mocha --timeout 20000 ./app/tests/middleware", + "test:middleware": "mocha --timeout 20000 ./app/tests/middleware --exit", "test:authn": "./app/tests/run-mocha-separate-jobs.sh ./app/tests/authn", "test:fuzz": "mocha --timeout 20000 --recursive ./app/tests/fuzz", "test:scheduler": "mocha --timeout 60000 --recursive ./app/tests/scheduler" From 8094564ebffe1db1f068a4298c387c5fef174c1c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:18:33 -0400 Subject: [PATCH 034/370] build(semantic-release): run on adm branch --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92637f3f..4dd17afc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ on: - alpha - '*.*.x' # Matches branches like '1.2.x', '2.3.x' - '*.x' # Matches branches like '1.x', '2.x' + - adm # TODO temporary project branch, remove when done pull_request: # Run on all PRs regardless of target branch workflow_dispatch: From 3130296e463a824ff3af6aad612f00b748f659ef Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 25 Oct 2025 00:22:44 -0500 Subject: [PATCH 035/370] feat: add import bundle streaming capability --- .../paths/collection-bundles-paths.yml | 8 + .../collection-bundles-controller.js | 168 ++++++++++++++++++ app/routes/collection-bundles-routes.js | 12 +- .../import-bundle.js | 112 ++++++++++-- .../collection-bundles.spec.js | 111 ++++++++++++ 5 files changed, 393 insertions(+), 18 deletions(-) diff --git a/app/api/definitions/paths/collection-bundles-paths.yml b/app/api/definitions/paths/collection-bundles-paths.yml index fc774451..05ca2f40 100644 --- a/app/api/definitions/paths/collection-bundles-paths.yml +++ b/app/api/definitions/paths/collection-bundles-paths.yml @@ -70,6 +70,14 @@ paths: schema: type: boolean default: false + - name: stream + in: query + description: | + Stream import progress updates using Server-Sent Events (SSE). + When enabled, the endpoint will stream progress events and the final result. + schema: + type: boolean + default: false - name: forceImport in: query description: | diff --git a/app/controllers/collection-bundles-controller.js b/app/controllers/collection-bundles-controller.js index d6c60af5..2894e525 100644 --- a/app/controllers/collection-bundles-controller.js +++ b/app/controllers/collection-bundles-controller.js @@ -25,6 +25,174 @@ function extractForceImportParameters(req) { return params; } +/** + * Stream import progress using Server-Sent Events (SSE) + */ +exports.streamImportBundle = async function (req, res) { + let heartbeatInterval; + + try { + const collectionBundleData = req.body; + const forceImportParameters = extractForceImportParameters(req); + + // Set up SSE headers + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no', + }); + + // Heartbeat to prevent connection timeout + heartbeatInterval = setInterval(() => { + if (!res.destroyed) { + res.write(': heartbeat\n\n'); + } + }, 30000); + + // Handle client disconnect + req.on('close', () => { + if (heartbeatInterval) clearInterval(heartbeatInterval); + }); + + // Validate bundle (same validation as regular import) + const errorResult = { + bundleErrors: { + noCollection: false, + moreThanOneCollection: false, + duplicateCollection: false, + badlyFormattedCollection: false, + }, + objectErrors: { + summary: { + duplicateObjectInBundleCount: 0, + invalidAttackSpecVersionCount: 0, + }, + errors: [], + }, + }; + let errorFound = false; + + const collections = collectionBundleData.objects.filter( + (object) => object.type === 'x-mitre-collection', + ); + + if (collections.length === 0) { + logger.warn('Collection bundle is missing x-mitre-collection object.'); + errorResult.bundleErrors.noCollection = true; + errorFound = true; + } else if (collections.length > 1) { + logger.warn('Collection bundle has more than one x-mitre-collection object.'); + errorResult.bundleErrors.moreThanOneCollection = true; + errorFound = true; + } + + if (collections.length > 0 && !collections[0].id) { + logger.warn('Badly formatted collection in bundle, x-mitre-collection missing id.'); + errorResult.bundleErrors.badlyFormattedCollection = true; + errorFound = true; + } + + const validationResult = collectionBundlesService.validateBundle(collectionBundleData); + if (validationResult.errors.length > 0) { + errorFound = true; + if (validationResult.duplicateObjectInBundleCount > 0) { + logger.warn( + `Collection bundle has ${validationResult.duplicateObjectInBundleCount} duplicate objects.`, + ); + errorResult.objectErrors.summary.duplicateObjectInBundleCount = + validationResult.duplicateObjectInBundleCount; + } + if (validationResult.invalidAttackSpecVersionCount > 0) { + logger.warn( + `Collection bundle has ${validationResult.invalidAttackSpecVersionCount} objects with invalid ATT&CK Spec version.`, + ); + errorResult.objectErrors.summary.invalidAttackSpecVersionCount = + validationResult.invalidAttackSpecVersionCount; + } + errorResult.objectErrors.errors = validationResult.errors; + } + + if (errorFound) { + if ( + errorResult.bundleErrors.noCollection || + errorResult.bundleErrors.moreThanOneCollection || + errorResult.bundleErrors.badlyFormattedCollection || + errorResult.objectErrors.summary.duplicateObjectInBundleCount > 0 + ) { + logger.error('Unable to import collection bundle due to an error in the bundle.'); + const event = `event: error\ndata: ${JSON.stringify(errorResult)}\n\n`; + res.write(event); + res.end(); + return; + } + + if ( + errorResult.objectErrors.summary.invalidAttackSpecVersionCount > 0 && + !forceImportParameters.find( + (e) => e === collectionBundlesService.forceImportParameters.attackSpecVersionViolations, + ) + ) { + logger.error('Unable to import collection bundle due to an error in the bundle.'); + const event = `event: error\ndata: ${JSON.stringify(errorResult)}\n\n`; + res.write(event); + res.end(); + return; + } + } + + // Progress callback to send SSE events + const onProgress = (progress) => { + if (!res.destroyed) { + const event = `event: progress\ndata: ${JSON.stringify(progress)}\n\n`; + res.write(event); + // Flush immediately to ensure event is sent to client + if (res.flush && typeof res.flush === 'function') { + res.flush(); + } + } + }; + + const options = { + previewOnly: req.query.previewOnly || req.query.checkOnly, + forceImportParameters, + onProgress, + }; + + // Import the collection bundle + const importedCollection = await collectionBundlesService.importBundle( + collections[0], + collectionBundleData, + options, + ); + + // Send final result + if (!res.destroyed) { + const event = `event: complete\ndata: ${JSON.stringify(importedCollection)}\n\n`; + res.write(event); + res.end(); + } + + logger.debug('Success: Imported collection with id ' + importedCollection.stix.id); + } catch (err) { + logger.error('Import failed with error: ' + err); + + if (heartbeatInterval) clearInterval(heartbeatInterval); + + if (!res.destroyed) { + const errorData = { + message: err.message || 'Unknown error', + error: err.toString(), + }; + const event = `event: error\ndata: ${JSON.stringify(errorData)}\n\n`; + res.write(event); + res.end(); + } + } finally { + if (heartbeatInterval) clearInterval(heartbeatInterval); + } +}; + exports.importBundle = async function (req, res) { // Get the data from the request const collectionBundleData = req.body; diff --git a/app/routes/collection-bundles-routes.js b/app/routes/collection-bundles-routes.js index a95c34aa..c5168c3c 100644 --- a/app/routes/collection-bundles-routes.js +++ b/app/routes/collection-bundles-routes.js @@ -8,6 +8,16 @@ const authz = require('../lib/authz-middleware'); const router = express.Router(); +// Middleware to route import requests to streaming or regular endpoint +const importBundleRouter = (req, res, next) => { + // Use streaming if requested + if (req.query.stream === 'true' || req.query.stream === true) { + return collectionBundlesController.streamImportBundle(req, res, next); + } + // Otherwise use regular import + return collectionBundlesController.importBundle(req, res, next); +}; + router .route('/collection-bundles') .get( @@ -18,7 +28,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher, [authz.serviceRoles.collectionManager]), - collectionBundlesController.importBundle, + importBundleRouter, ); module.exports = router; diff --git a/app/services/collection-bundles-service/import-bundle.js b/app/services/collection-bundles-service/import-bundle.js index d58af922..6a855212 100644 --- a/app/services/collection-bundles-service/import-bundle.js +++ b/app/services/collection-bundles-service/import-bundle.js @@ -270,7 +270,11 @@ async function processObjects( importReferences, referenceImportResults, ) { + const totalObjects = objects.length; + let processedObjects = 0; + for (const importObject of objects) { + processedObjects++; // Check if object is in x_mitre_contents if ( !contentsMap.delete(makeKeyFromObject(importObject)) && @@ -323,6 +327,18 @@ async function processObjects( importReferences, referenceImportResults, ); + + // Report progress if callback provided + // Throttle progress updates: report every 10 objects, at milestones, or on last object + const phasePercentage = Math.round((processedObjects / totalObjects) * 100); + const shouldReport = + processedObjects % 10 === 0 || // Every 10 objects + processedObjects === totalObjects || // Last object + phasePercentage % 5 === 0; // Every 5% milestone + + if (shouldReport) { + reportProgress(options, 'processing', processedObjects, totalObjects); + } } // Check for objects in x_mitre_contents but not in bundle @@ -340,6 +356,46 @@ async function processObjects( } } +/** + * Calculate overall progress percentage across all phases + * Phase allocation: processing=85%, references=10%, saving=5% + */ +function calculateOverallProgress(phase, phaseProgress) { + const phaseRanges = { + processing: { start: 0, end: 85 }, + references: { start: 85, end: 95 }, + saving: { start: 95, end: 100 }, + complete: { start: 100, end: 100 }, + }; + + const range = phaseRanges[phase]; + if (!range) return 0; + + const rangeSize = range.end - range.start; + return Math.round(range.start + (phaseProgress / 100) * rangeSize); +} + +/** + * Report progress via callback if provided + * @param {Object} options - Import options containing onProgress callback + * @param {string} phase - Current phase name + * @param {number} processed - Number of items processed + * @param {number} total - Total number of items + */ +function reportProgress(options, phase, processed, total) { + if (options.onProgress && typeof options.onProgress === 'function') { + const phasePercentage = total > 0 ? Math.round((processed / total) * 100) : 100; + const overallPercentage = calculateOverallProgress(phase, phasePercentage); + options.onProgress({ + phase, + processed, + total, + percentage: overallPercentage, + phasePercentage, + }); + } +} + /** * Import references found in the bundle * @param {Map} importReferences - Map of references to import @@ -347,10 +403,18 @@ async function processObjects( * @param {Object} importedCollection - Collection being imported */ async function importReferences(importReferences, options, importedCollection) { + const totalReferences = importReferences.size; + + // Report initial progress + reportProgress(options, 'references', 0, totalReferences); + const references = await referencesService.retrieveAll({}); const existingReferences = new Map(references.map((item) => [item.source_name, item])); + let processedReferences = 0; + for (const importReference of importReferences.values()) { + processedReferences++; if (existingReferences.has(importReference.source_name)) { // Update existing reference importedCollection.workspace.import_references.changes.push(importReference.source_name); @@ -364,6 +428,9 @@ async function importReferences(importReferences, options, importedCollection) { await referencesService.create(importReference); } } + + // Report progress + reportProgress(options, 'references', processedReferences, totalReferences); } } @@ -375,6 +442,10 @@ async function importReferences(importReferences, options, importedCollection) { * @returns {Promise} Saved collection */ async function saveCollection(importedCollection, duplicateCollection, options) { + // Report saving phase start + reportProgress(options, 'saving', 0, 1); + + let result; if (duplicateCollection) { // Add reimport results to existing collection const reimport = { @@ -389,30 +460,37 @@ async function saveCollection(importedCollection, duplicateCollection, options) duplicateCollection.workspace.reimports.push(reimport); if (!options.previewOnly) { - return Collection.findByIdAndUpdate(duplicateCollection._id, duplicateCollection, { + result = await Collection.findByIdAndUpdate(duplicateCollection._id, duplicateCollection, { new: true, lean: true, }); + } else { + result = importedCollection; } - return importedCollection; - } - - // Create new collection - if (!options.previewOnly) { - try { - const result = await collectionsService.create(importedCollection, { - addObjectsToCollection: false, - import: true, - }); - return result.savedCollection; - } catch (err) { - if (err.name === 'MongoServerError' && err.code === 11000) { - throw new Error(errors.duplicateCollection); + } else { + // Create new collection + if (!options.previewOnly) { + try { + const createResult = await collectionsService.create(importedCollection, { + addObjectsToCollection: false, + import: true, + }); + result = createResult.savedCollection; + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new Error(errors.duplicateCollection); + } + throw err; } - throw err; + } else { + result = importedCollection; } } - return importedCollection; + + // Report completion + reportProgress(options, 'complete', 1, 1); + + return result; } /** diff --git a/app/tests/api/collection-bundles/collection-bundles.spec.js b/app/tests/api/collection-bundles/collection-bundles.spec.js index 40d5aade..23351b77 100644 --- a/app/tests/api/collection-bundles/collection-bundles.spec.js +++ b/app/tests/api/collection-bundles/collection-bundles.spec.js @@ -808,3 +808,114 @@ describe('Collection Bundles Basic API', function () { await database.closeConnection(); }); }); + +describe('Collection Bundles Streaming API', function () { + let app; + let passportCookie; + + before(async function () { + // Establish the database connection + await database.initializeConnection(); + + // Check for a valid database configuration + await databaseConfiguration.checkSystemConfiguration(); + + // Initialize the express app + app = await require('../../../index').initializeApp(); + + // Log into the app + passportCookie = await login.loginAnonymous(app); + }); + + it('POST /api/collection-bundles?stream=true returns SSE headers', async function () { + const body = collectionBundleData; + + // Just verify we get SSE headers back - don't try to parse the full stream + const response = await request(app) + .post('/api/collection-bundles?stream=true') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + + // Verify SSE headers + expect(response.status).toBe(200); + expect(response.headers['content-type']).toBe('text/event-stream'); + expect(response.headers['cache-control']).toBe('no-cache'); + expect(response.headers.connection).toBe('keep-alive'); + }); + + it('POST /api/collection-bundles?stream=true returns SSE headers for errors', async function () { + const body = collectionBundleData3; // Empty bundle with no collection + + const response = await request(app) + .post('/api/collection-bundles?stream=true') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + + // Should still return SSE headers even for errors + expect(response.status).toBe(200); + expect(response.headers['content-type']).toBe('text/event-stream'); + }); + + it('POST /api/collection-bundles?stream=true&previewOnly=true returns SSE headers', async function () { + const body = collectionBundleData; + + const response = await request(app) + .post('/api/collection-bundles?stream=true&previewOnly=true') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + + expect(response.status).toBe(200); + expect(response.headers['content-type']).toBe('text/event-stream'); + }); + + it('POST /api/collection-bundles without stream parameter uses regular import (no SSE)', async function () { + // Delete the previously imported collection so we can reimport it + const timestamp = new Date().toISOString(); + const updatedBundle = _.cloneDeep(collectionBundleData); + updatedBundle.objects[0].modified = timestamp; + updatedBundle.objects[0].id = 'x-mitre-collection--aaaaaaaa-0a05-4d9e-ab54-9b8563669647'; + + const body = updatedBundle; + const response = await request(app) + .post('/api/collection-bundles') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + // Verify standard JSON response, not SSE + expect(response.headers['content-type']).toMatch(/json/); + expect(response.headers['content-type']).not.toBe('text/event-stream'); + + // Verify we got a collection object directly + const collection = response.body; + expect(collection).toBeDefined(); + expect(collection.workspace).toBeDefined(); + expect(collection.stix).toBeDefined(); + }); + + it('POST /api/collection-bundles?stream=true with forceImport returns SSE headers', async function () { + const timestamp = new Date().toISOString(); + const uniqueBundle = _.cloneDeep(collectionBundleData); + uniqueBundle.objects[0].modified = timestamp; + uniqueBundle.objects[0].id = 'x-mitre-collection--bbbbbbbb-0a05-4d9e-ab54-9b8563669647'; + + const body = uniqueBundle; + const response = await request(app) + .post('/api/collection-bundles?stream=true&forceImport=duplicate-collection') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + + expect(response.status).toBe(200); + expect(response.headers['content-type']).toBe('text/event-stream'); + }); + + after(async function () { + await database.closeConnection(); + }); +}); From c2cb1c7b49e0a52ef6dc197e69ac560f7b9d7763 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 25 Oct 2025 10:39:31 -0500 Subject: [PATCH 036/370] refactor: refactor duplicate code related to importing bundles --- .../collection-bundles-controller.js | 293 ++++++++---------- 1 file changed, 126 insertions(+), 167 deletions(-) diff --git a/app/controllers/collection-bundles-controller.js b/app/controllers/collection-bundles-controller.js index 2894e525..a10201e4 100644 --- a/app/controllers/collection-bundles-controller.js +++ b/app/controllers/collection-bundles-controller.js @@ -25,6 +25,115 @@ function extractForceImportParameters(req) { return params; } +function createErrorResult() { + return { + bundleErrors: { + noCollection: false, + moreThanOneCollection: false, + duplicateCollection: false, + badlyFormattedCollection: false, + }, + objectErrors: { + summary: { + duplicateObjectInBundleCount: 0, + invalidAttackSpecVersionCount: 0, + }, + errors: [], + }, + }; +} + +/** + * Validates the structure of a collection bundle + * @param {Object} collectionBundleData - The bundle data to validate + * @returns {Object} Validation result with { errorResult, errorFound, collections } + */ +function validateCollectionBundle(collectionBundleData) { + const errorResult = createErrorResult(); + let errorFound = false; + + // Find the x-mitre-collection objects + const collections = collectionBundleData.objects.filter( + (object) => object.type === 'x-mitre-collection', + ); + + // The bundle must have an x-mitre-collection object + if (collections.length === 0) { + logger.warn('Collection bundle is missing x-mitre-collection object.'); + errorResult.bundleErrors.noCollection = true; + errorFound = true; + } else if (collections.length > 1) { + logger.warn('Collection bundle has more than one x-mitre-collection object.'); + errorResult.bundleErrors.moreThanOneCollection = true; + errorFound = true; + } + + // The collection must have an id + if (collections.length > 0 && !collections[0].id) { + logger.warn('Badly formatted collection in bundle, x-mitre-collection missing id.'); + errorResult.bundleErrors.badlyFormattedCollection = true; + errorFound = true; + } + + // Validate bundle content + const validationResult = collectionBundlesService.validateBundle(collectionBundleData); + if (validationResult.errors.length > 0) { + errorFound = true; + if (validationResult.duplicateObjectInBundleCount > 0) { + logger.warn( + `Collection bundle has ${validationResult.duplicateObjectInBundleCount} duplicate objects.`, + ); + errorResult.objectErrors.summary.duplicateObjectInBundleCount = + validationResult.duplicateObjectInBundleCount; + } + if (validationResult.invalidAttackSpecVersionCount > 0) { + logger.warn( + `Collection bundle has ${validationResult.invalidAttackSpecVersionCount} objects with invalid ATT&CK Spec version.`, + ); + errorResult.objectErrors.summary.invalidAttackSpecVersionCount = + validationResult.invalidAttackSpecVersionCount; + } + errorResult.objectErrors.errors.push(...validationResult.errors); + } + + return { errorResult, errorFound, collections }; +} + +/** + * Checks if validation errors should prevent import + * @param {Object} errorResult - The error result from validation + * @param {boolean} errorFound - Whether any errors were found + * @param {Array} forceImportParameters - Parameters to override validation errors + * @returns {boolean} True if import should be blocked + */ +function shouldBlockImport(errorResult, errorFound, forceImportParameters) { + if (!errorFound) { + return false; + } + + // These errors do not have forceImport flags yet + if ( + errorResult.bundleErrors.noCollection || + errorResult.bundleErrors.moreThanOneCollection || + errorResult.bundleErrors.badlyFormattedCollection || + errorResult.objectErrors.summary.duplicateObjectInBundleCount > 0 + ) { + return true; + } + + // Check the forceImport flag for overriding ATT&CK Spec version violations + if ( + errorResult.objectErrors.summary.invalidAttackSpecVersionCount > 0 && + !forceImportParameters.find( + (e) => e === collectionBundlesService.forceImportParameters.attackSpecVersionViolations, + ) + ) { + return true; + } + + return false; +} + /** * Stream import progress using Server-Sent Events (SSE) */ @@ -55,90 +164,18 @@ exports.streamImportBundle = async function (req, res) { if (heartbeatInterval) clearInterval(heartbeatInterval); }); - // Validate bundle (same validation as regular import) - const errorResult = { - bundleErrors: { - noCollection: false, - moreThanOneCollection: false, - duplicateCollection: false, - badlyFormattedCollection: false, - }, - objectErrors: { - summary: { - duplicateObjectInBundleCount: 0, - invalidAttackSpecVersionCount: 0, - }, - errors: [], - }, - }; - let errorFound = false; - - const collections = collectionBundleData.objects.filter( - (object) => object.type === 'x-mitre-collection', + // Validate bundle using shared validation logic + const { errorResult, errorFound, collections } = validateCollectionBundle( + collectionBundleData ); - if (collections.length === 0) { - logger.warn('Collection bundle is missing x-mitre-collection object.'); - errorResult.bundleErrors.noCollection = true; - errorFound = true; - } else if (collections.length > 1) { - logger.warn('Collection bundle has more than one x-mitre-collection object.'); - errorResult.bundleErrors.moreThanOneCollection = true; - errorFound = true; - } - - if (collections.length > 0 && !collections[0].id) { - logger.warn('Badly formatted collection in bundle, x-mitre-collection missing id.'); - errorResult.bundleErrors.badlyFormattedCollection = true; - errorFound = true; - } - - const validationResult = collectionBundlesService.validateBundle(collectionBundleData); - if (validationResult.errors.length > 0) { - errorFound = true; - if (validationResult.duplicateObjectInBundleCount > 0) { - logger.warn( - `Collection bundle has ${validationResult.duplicateObjectInBundleCount} duplicate objects.`, - ); - errorResult.objectErrors.summary.duplicateObjectInBundleCount = - validationResult.duplicateObjectInBundleCount; - } - if (validationResult.invalidAttackSpecVersionCount > 0) { - logger.warn( - `Collection bundle has ${validationResult.invalidAttackSpecVersionCount} objects with invalid ATT&CK Spec version.`, - ); - errorResult.objectErrors.summary.invalidAttackSpecVersionCount = - validationResult.invalidAttackSpecVersionCount; - } - errorResult.objectErrors.errors = validationResult.errors; - } - - if (errorFound) { - if ( - errorResult.bundleErrors.noCollection || - errorResult.bundleErrors.moreThanOneCollection || - errorResult.bundleErrors.badlyFormattedCollection || - errorResult.objectErrors.summary.duplicateObjectInBundleCount > 0 - ) { - logger.error('Unable to import collection bundle due to an error in the bundle.'); - const event = `event: error\ndata: ${JSON.stringify(errorResult)}\n\n`; - res.write(event); - res.end(); - return; - } - - if ( - errorResult.objectErrors.summary.invalidAttackSpecVersionCount > 0 && - !forceImportParameters.find( - (e) => e === collectionBundlesService.forceImportParameters.attackSpecVersionViolations, - ) - ) { - logger.error('Unable to import collection bundle due to an error in the bundle.'); - const event = `event: error\ndata: ${JSON.stringify(errorResult)}\n\n`; - res.write(event); - res.end(); - return; - } + // Check if import should be blocked + if (shouldBlockImport(errorResult, errorFound, forceImportParameters)) { + logger.error('Unable to import collection bundle due to an error in the bundle.'); + const event = `event: error\ndata: ${JSON.stringify(errorResult)}\n\n`; + res.write(event); + res.end(); + return; } // Progress callback to send SSE events @@ -196,95 +233,17 @@ exports.streamImportBundle = async function (req, res) { exports.importBundle = async function (req, res) { // Get the data from the request const collectionBundleData = req.body; - const forceImportParameters = extractForceImportParameters(req); - const errorResult = { - bundleErrors: { - noCollection: false, - moreThanOneCollection: false, - duplicateCollection: false, - badlyFormattedCollection: false, - }, - objectErrors: { - summary: { - duplicateObjectInBundleCount: 0, - invalidAttackSpecVersionCount: 0, - }, - errors: [], - }, - }; - let errorFound = false; - - // Find the x-mitre-collection objects - const collections = collectionBundleData.objects.filter( - (object) => object.type === 'x-mitre-collection', + // Validate bundle using shared validation logic + const { errorResult, errorFound, collections } = validateCollectionBundle( + collectionBundleData ); - // The bundle must have an x-mitre-collection object - if (collections.length === 0) { - logger.warn('Collection bundle is missing x-mitre-collection object.'); - errorResult.bundleErrors.noCollection = true; - errorFound = true; - } else if (collections.length > 1) { - logger.warn('Collection bundle has more than one x-mitre-collection object.'); - errorResult.bundleErrors.moreThanOneCollection = true; - errorFound = true; - } - - // The collection must have an id. - if (collections.length > 0 && !collections[0].id) { - logger.warn('Badly formatted collection in bundle, x-mitre-collection missing id.'); - errorResult.bundleErrors.badlyFormattedCollection = true; - errorFound = true; - } - - const validationResult = collectionBundlesService.validateBundle(collectionBundleData); - if (validationResult.errors.length > 0) { - errorFound = true; - if (validationResult.duplicateObjectInBundleCount > 0) { - logger.warn( - `Collection bundle has ${validationResult.duplicateObjectInBundleCount} duplicate objects.`, - ); - errorResult.objectErrors.summary.duplicateObjectInBundleCount = - validationResult.duplicateObjectInBundleCount; - } - - if (validationResult.invalidAttackSpecVersionCount > 0) { - logger.warn( - `Collection bundle has ${validationResult.invalidAttackSpecVersionCount} objects with invalid ATT&CK Spec Versions.`, - ); - errorResult.objectErrors.summary.invalidAttackSpecVersionCount = - validationResult.invalidAttackSpecVersionCount; - } - - errorResult.objectErrors.errors.push(...validationResult.errors); - } - - if (errorFound) { - // Determine if any of the errors are overridden by the forceImport flag - - // These errors do not have forceImport flags yet - if ( - errorResult.bundleErrors.noCollection || - errorResult.bundleErrors.moreThanOneCollection || - errorResult.bundleErrors.badlyFormattedCollection || - errorResult.objectErrors.summary.duplicateObjectInBundleCount > 0 - ) { - logger.error('Unable to import collection bundle due to an error in the bundle.'); - return res.status(400).send(errorResult); - } - - // Check the forceImport flag for overriding ATT&CK Spec version violations - if ( - errorResult.objectErrors.summary.invalidAttackSpecVersionCount > 0 && - !forceImportParameters.find( - (e) => e === collectionBundlesService.forceImportParameters.attackSpecVersionViolations, - ) - ) { - logger.error('Unable to import collection bundle due to an error in the bundle.'); - return res.status(400).send(errorResult); - } + // Check if import should be blocked + if (shouldBlockImport(errorResult, errorFound, forceImportParameters)) { + logger.error('Unable to import collection bundle due to an error in the bundle.'); + return res.status(400).send(errorResult); } const options = { From e11b5da72d440cad28870ecd873318d8b99a31ef Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 25 Oct 2025 10:40:17 -0500 Subject: [PATCH 037/370] chore: run prettier --- app/controllers/collection-bundles-controller.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/collection-bundles-controller.js b/app/controllers/collection-bundles-controller.js index a10201e4..29e29883 100644 --- a/app/controllers/collection-bundles-controller.js +++ b/app/controllers/collection-bundles-controller.js @@ -165,9 +165,7 @@ exports.streamImportBundle = async function (req, res) { }); // Validate bundle using shared validation logic - const { errorResult, errorFound, collections } = validateCollectionBundle( - collectionBundleData - ); + const { errorResult, errorFound, collections } = validateCollectionBundle(collectionBundleData); // Check if import should be blocked if (shouldBlockImport(errorResult, errorFound, forceImportParameters)) { @@ -236,9 +234,7 @@ exports.importBundle = async function (req, res) { const forceImportParameters = extractForceImportParameters(req); // Validate bundle using shared validation logic - const { errorResult, errorFound, collections } = validateCollectionBundle( - collectionBundleData - ); + const { errorResult, errorFound, collections } = validateCollectionBundle(collectionBundleData); // Check if import should be blocked if (shouldBlockImport(errorResult, errorFound, forceImportParameters)) { From 58f7dfb89db17f2e4ae35966b8d155801ab37d15 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 26 Oct 2025 01:46:43 -0500 Subject: [PATCH 038/370] feat: save user sessions in mongo across docker container reboots --- app/index.js | 7 +- package-lock.json | 434 ++++++++++++++++++++++++++++++++++++++++------ package.json | 1 + 3 files changed, 389 insertions(+), 53 deletions(-) diff --git a/app/index.js b/app/index.js index bf8c2c7d..fa525ebe 100644 --- a/app/index.js +++ b/app/index.js @@ -129,12 +129,17 @@ exports.initializeApp = async function () { } // Configure server-side sessions - // TBD: Replace default MemoryStore with production quality session storage const session = require('express-session'); + const MongoStore = require('connect-mongo'); const sessionOptions = { secret: config.session.secret, resave: false, saveUninitialized: false, + store: MongoStore.create({ + client: require('mongoose').connection.getClient(), + dbName: config.database.dbName, + collectionName: 'sessions' + }) }; app.use(session(sessionOptions)); diff --git a/package-lock.json b/package-lock.json index 63aa9e61..0a6b1a4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", "compression": "^1.8.1", + "connect-mongo": "^4.6.0", "convict": "^6.2.4", "cors": "^2.8.5", "express": "^4.21.2", @@ -2010,7 +2011,9 @@ "license": "MIT" }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -2885,12 +2888,18 @@ }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", "license": "MIT" }, "node_modules/@types/whatwg-url": { - "version": "11.0.5", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", "license": "MIT", + "peer": true, "dependencies": { + "@types/node": "*", "@types/webidl-conversions": "*" } }, @@ -3101,6 +3110,18 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/async": { "version": "3.2.6", "license": "MIT" @@ -3149,6 +3170,27 @@ "license": "Apache-2.0", "optional": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/basic-auth": { "version": "2.0.1", "license": "MIT", @@ -3166,6 +3208,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -3273,6 +3321,31 @@ "node": ">=16.20.1" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -3835,6 +3908,45 @@ "proto-list": "~1.2.1" } }, + "node_modules/connect-mongo": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", + "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "mongodb": "^4.1.0" + } + }, + "node_modules/connect-mongo/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/connect-mongo/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -6323,6 +6435,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6463,7 +6596,6 @@ "node_modules/ip": { "version": "2.0.0", "license": "MIT", - "optional": true, "peer": true }, "node_modules/ipaddr.js": { @@ -6946,6 +7078,18 @@ "json-buffer": "3.0.1" } }, + "node_modules/kruptein": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.8.tgz", + "integrity": "sha512-0CyalFA0Cjp3jnziMp0u1uLZW2/ouhQ0mEMfYlroBXNe86na1RwAuwBcdRAegeWZNMfQy/G5fN47g/Axjtqrfw==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, "node_modules/kuler": { "version": "2.0.0", "license": "MIT" @@ -7482,6 +7626,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7724,57 +7874,33 @@ } }, "node_modules/mongodb": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", - "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", + "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.3", - "mongodb-connection-string-url": "^3.0.0" + "bson": "^4.7.2", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" }, "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" + "node": ">=12.9.0" }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } + "optionalDependencies": { + "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0" } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" } }, "node_modules/mongodb-memory-server": { @@ -7816,6 +7942,16 @@ "node": ">=16.20.1" } }, + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/mongodb-memory-server-core/node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -7858,6 +7994,64 @@ "node": ">= 14" } }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/mongodb-memory-server-core/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7865,6 +8059,46 @@ "dev": true, "license": "MIT" }, + "node_modules/mongodb-memory-server-core/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/mongoose": { "version": "8.15.1", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.1.tgz", @@ -7887,10 +8121,100 @@ "url": "https://opencollective.com/mongoose" } }, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", + "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, "node_modules/mongoose/node_modules/ms": { "version": "2.1.3", "license": "MIT" }, + "node_modules/mongoose/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/morgan": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", @@ -12549,7 +12873,6 @@ "node_modules/smart-buffer": { "version": "4.2.0", "license": "MIT", - "optional": true, "peer": true, "engines": { "node": ">= 6.0.0", @@ -12577,7 +12900,6 @@ "node_modules/socks": { "version": "2.7.1", "license": "MIT", - "optional": true, "peer": true, "dependencies": { "ip": "^2.0.0", @@ -13167,13 +13489,16 @@ } }, "node_modules/tr46": { - "version": "4.1.1", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "license": "MIT", + "peer": true, "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.1.1" }, "engines": { - "node": ">=14" + "node": ">=12" } }, "node_modules/traverse": { @@ -13454,20 +13779,25 @@ }, "node_modules/webidl-conversions": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "license": "BSD-2-Clause", "engines": { "node": ">=12" } }, "node_modules/whatwg-url": { - "version": "13.0.0", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "license": "MIT", + "peer": true, "dependencies": { - "tr46": "^4.1.1", + "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=12" } }, "node_modules/which": { diff --git a/package.json b/package.json index 2799addb..5643216c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", "compression": "^1.8.1", + "connect-mongo": "^4.6.0", "convict": "^6.2.4", "cors": "^2.8.5", "express": "^4.21.2", From 41156b20cd5b9c66d6304b742403f0fecf83517b Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 26 Oct 2025 01:47:06 -0500 Subject: [PATCH 039/370] chore: prettier --- app/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/index.js b/app/index.js index fa525ebe..8cdf73e1 100644 --- a/app/index.js +++ b/app/index.js @@ -138,8 +138,8 @@ exports.initializeApp = async function () { store: MongoStore.create({ client: require('mongoose').connection.getClient(), dbName: config.database.dbName, - collectionName: 'sessions' - }) + collectionName: 'sessions', + }), }; app.use(session(sessionOptions)); From b1a632858ab6c5f796d41033c2dac68ae6067b26 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:59:51 -0400 Subject: [PATCH 040/370] feat(attack-id): add REST endpoint for ATT&CK ID generation - Add GET /api/attack-objects/attack-id/next endpoint for generating next available ATT&CK IDs - Create app/lib/attack-id-generator.js utility module with ID generation and validation functions - generateAttackId: generates next sequential ID for STIX type - attackIdInWorkspaceStixObject: validates and checks for ID conflicts - Support for subtechnique IDs (e.g., T1234.001) with parentRef parameter - Integrate ID generation/validation into BaseService.create() method - Auto-generate IDs when not provided - Validate IDs when provided by client - Require explicit IDs for subtechniques (no auto-generation in create flow) - Add OpenAPI specification for new endpoint in attack-objects-paths.yml - Add comprehensive integration tests in attack-objects-attack-id.spec.js - Test ID generation for all supported STIX types - Test sequential ID numbering and gap handling - Test subtechnique ID generation with parent references This centralizes ATT&CK ID generation logic in the backend to prevent collisions and ensure consistency across all clients. --- app/api/definitions/openapi.yml | 4 + .../paths/attack-objects-paths.yml | 67 ++++ app/controllers/attack-objects-controller.js | 29 ++ app/lib/attack-id-generator.js | 284 +++++++++++++++++ app/routes/attack-objects-routes.js | 8 + app/services/_base.service.js | 24 ++ app/services/attack-objects-service.js | 61 ++++ .../attack-objects-attack-id.spec.js | 287 ++++++++++++++++++ 8 files changed, 764 insertions(+) create mode 100644 app/lib/attack-id-generator.js create mode 100644 app/tests/api/attack-objects/attack-objects-attack-id.spec.js diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 7d693c76..10b0ea9e 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -71,6 +71,10 @@ paths: # ATT&CK Objects /api/attack-objects: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects' + + # Generate next available ATT&CK ID + /api/attack-objects/attack-id/next: + $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects~1attack-id~1next' # Techniques /api/techniques: diff --git a/app/api/definitions/paths/attack-objects-paths.yml b/app/api/definitions/paths/attack-objects-paths.yml index 13051727..2b4c536f 100644 --- a/app/api/definitions/paths/attack-objects-paths.yml +++ b/app/api/definitions/paths/attack-objects-paths.yml @@ -123,3 +123,70 @@ paths: - $ref: '../components/software.yml#/components/schemas/software' - $ref: '../components/tactics.yml#/components/schemas/tactic' - $ref: '../components/techniques.yml#/components/schemas/technique' + /api/attack-objects/attack-id/next: + get: + summary: 'Get the next available ATT&CK ID for a STIX type' + operationId: 'attack-object-get-next-attack-id' + description: | + This endpoint generates the next available ATT&CK ID for a given STIX type. + This is useful for previewing what ID will be assigned before creating a new object. + tags: + - 'ATT&CK Objects' + parameters: + - name: type + in: query + required: true + description: | + The STIX type for which to generate an ATT&CK ID. + Must be a type that supports ATT&CK IDs. + schema: + type: string + enum: + - x-mitre-tactic + - attack-pattern + - intrusion-set + - malware + - tool + - course-of-action + - x-mitre-data-source + - x-mitre-data-component + - x-mitre-asset + - campaign + - x-mitre-detection-strategy + - x-mitre-analytic + example: 'x-mitre-tactic' + - name: parentRef + in: query + required: false + description: | + The STIX ID of the parent technique (required for generating subtechnique IDs). + Only applicable when type is 'attack-pattern' and you want a subtechnique ID. + schema: + type: string + example: 'attack-pattern--12345678-1234-1234-1234-123456789abc' + responses: + '200': + description: 'The next available ATT&CK ID for the specified type.' + content: + application/json: + schema: + type: object + properties: + attack_id: + type: string + description: 'The next available ATT&CK ID' + example: 'TA0042' + '400': + description: 'Bad request - missing or invalid type parameter, or type does not support ATT&CK IDs' + content: + text/plain: + schema: + type: string + example: 'Missing required query parameter: type' + '500': + description: 'Server error - unable to generate ATT&CK ID' + content: + text/plain: + schema: + type: string + example: 'Unable to generate next ATT&CK ID. Server error.' diff --git a/app/controllers/attack-objects-controller.js b/app/controllers/attack-objects-controller.js index 58d99d50..1fa07e2f 100644 --- a/app/controllers/attack-objects-controller.js +++ b/app/controllers/attack-objects-controller.js @@ -34,3 +34,32 @@ exports.retrieveAll = async function (req, res) { return res.status(500).send('Unable to get ATT&CK objects. Server error.'); } }; + +exports.getNextAttackId = async function (req, res) { + // Validate required query parameter + if (!req.query.type) { + return res.status(400).send('Missing required query parameter: type'); + } + + const stixType = req.query.type; + const parentRef = req.query.parentRef; + + // Validate parentRef for subtechniques + if (parentRef && stixType !== 'attack-pattern') { + return res.status(400).send('parentRef parameter is only valid for attack-pattern type'); + } + + try { + const nextAttackId = await attackObjectsService.getNextAttackId(stixType, parentRef); + + if (!nextAttackId) { + return res.status(400).send(`STIX type '${stixType}' does not support ATT&CK IDs`); + } + + logger.debug(`Success: Generated next ATT&CK ID ${nextAttackId} for type ${stixType}`); + return res.status(200).send({ attack_id: nextAttackId }); + } catch (err) { + logger.error('Failed to generate next ATT&CK ID with error: ' + err); + return res.status(500).send('Unable to generate next ATT&CK ID. Server error.'); + } +}; diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js new file mode 100644 index 00000000..5034d99b --- /dev/null +++ b/app/lib/attack-id-generator.js @@ -0,0 +1,284 @@ +'use strict'; + +const { + stixTypeToAttackIdMapping, + attackIdExamples, + createAttackIdSchema, +} = require('@mitre-attack/attack-data-model/dist/schemas/common/attack-id'); +const { InvalidTypeError, DuplicateIdError } = require('../exceptions'); +const logger = require('./logger'); + +/** + * Get the type prefix for an ATT&CK ID type + * @param {string} attackIdType - The ATT&CK ID type (e.g., 'technique', 'tactic') + * @returns {string} The type prefix (e.g., 'T', 'TA', 'G') + * @private + */ +function getTypePrefix(attackIdType) { + // Extract the prefix from the example format (e.g., "T####" -> "T") + const example = attackIdExamples[attackIdType]; + return example.split('#')[0]; +} + +/** + * Extract ATT&CK ID from STIX external_references (if present) + * @param {object} stix - The STIX object + * @returns {string|null} The external ATT&CK ID or null if not found + */ +function extractAttackIdFromExternalReferences(stix) { + if (!stix.external_references || stix.external_references.length === 0) { + return null; + } + + const attackSources = ['enterprise-attack', 'mobile-attack', 'ics-attack']; + const attackRef = stix.external_references.find( + (ref) => ref.source_name && attackSources.includes(ref.source_name) && ref.external_id, + ); + + return attackRef ? attackRef.external_id : null; +} +exports.extractAttackIdFromExternalReferences = extractAttackIdFromExternalReferences; + +/** + * Check if an ATT&CK ID is available (not already in use) + * @param {string} attackId - The ATT&CK ID to check + * @param {object} repository - The repository for querying existing objects + * @returns {Promise} True if the ID is available, false if already in use + */ +async function isAttackIdAvailable(attackId, repository) { + const allObjects = await repository.retrieveAll({}); + return !allObjects.some((obj) => obj.workspace?.attack_id === attackId); +} +exports.isAttackIdAvailable = isAttackIdAvailable; + +/** + * Validate that an ATT&CK ID matches the expected format for a STIX type + * @param {string} attackId - The ATT&CK ID to validate + * @param {string} stixType - The STIX type + * @returns {boolean} True if valid, false otherwise + */ +function validateAttackIdFormat(attackId, stixType) { + const schema = createAttackIdSchema(stixType); + const result = schema.safeParse(attackId); + return result.success; +} +exports.validateAttackIdFormat = validateAttackIdFormat; + +/** + * Validate an ATT&CK ID provided by the client + * @param {string} attackId - The ATT&CK ID to validate + * @param {string} stixType - The STIX type + * @param {object} repository - The repository for querying existing objects + * @returns {Promise} The validated ATT&CK ID + * @throws {Error} If the ID is invalid or already in use + * @private + */ +async function validateAttackId(attackId, stixType, repository) { + // Validate format + if (!validateAttackIdFormat(attackId, stixType)) { + throw new Error(`Invalid ATT&CK ID format: ${attackId} for STIX type ${stixType}`); + } + + // Check availability + const available = await isAttackIdAvailable(attackId, repository); + if (!available) { + throw new DuplicateIdError(`ATT&CK ID ${attackId} is already in use`); + } + + logger.debug(`Validated client-provided ATT&CK ID: ${attackId}`); + return attackId; +} + +/** + * Generate a new ATT&CK ID for an object + * + * This function generates unique ATT&CK IDs by finding the highest existing ID + * in the workspace.attack_id field for the given type and incrementing it. + * + * @param {string} stixType - The STIX type of the object (e.g., 'attack-pattern', 'intrusion-set', 'x-mitre-tactic') + * @param {object} repository - The repository for querying existing objects of this type + * @param {boolean} isSubtechnique - Whether to generate a subtechnique ID (only valid for attack-pattern) + * @param {string} parentTechniqueAttackId - Parent technique ATT&CK ID (required if isSubtechnique is true) + * @returns {Promise} The generated ATT&CK ID + * + * @example + * const tacticId = await generateAttackId('x-mitre-tactic', tacticsRepository, false); + * // Returns: "TA0042" + * + * @example + * const techniqueId = await generateAttackId('attack-pattern', techniquesRepository, false); + * // Returns: "T1234" + * + * @example + * const subtechniqueId = await generateAttackId('attack-pattern', techniquesRepository, true, 'T1234'); + * // Returns: "T1234.001" + */ +async function generateAttackId( + stixType, + repository, + isSubtechnique = false, + parentTechniqueAttackId = null, +) { + // Validate that the STIX type supports ATT&CK IDs + if (!(stixType in stixTypeToAttackIdMapping)) { + throw new InvalidTypeError(`STIX type '${stixType}' does not support ATT&CK ID generation`); + } + + // Handle subtechnique generation + if (isSubtechnique) { + if (stixType !== 'attack-pattern') { + throw new Error('Subtechniques are only valid for attack-pattern STIX type'); + } + if (!parentTechniqueAttackId) { + throw new Error('Parent technique ATT&CK ID is required for subtechnique generation'); + } + + // Validate parent ID format (must be T####) + if (!/^T\d{4}$/.test(parentTechniqueAttackId)) { + throw new Error( + `Invalid parent technique ATT&CK ID format: ${parentTechniqueAttackId}. Must be T####`, + ); + } + + logger.debug(`Generating subtechnique ID for parent: ${parentTechniqueAttackId}`); + + // Get all existing techniques + const results = await repository.retrieveAll({ + includeRevoked: true, + includeDeprecated: true, + }); + const allTechniques = results[0]?.documents || []; + + // Find all subtechniques for this parent + const existingSubtechniqueNumbers = allTechniques + .filter((tech) => { + if (!tech.stix.x_mitre_is_subtechnique) return false; + + const attackId = tech.workspace?.attack_id; + if (!attackId) return false; + + // Check if this subtechnique belongs to our parent + return attackId.startsWith(`${parentTechniqueAttackId}.`); + }) + .map((tech) => { + const attackId = tech.workspace.attack_id; + // Extract the 3-digit subtechnique number (e.g., "001" from "T1234.001") + const match = attackId.match(/\.(\d{3})$/); + return match ? parseInt(match[1], 10) : 0; + }) + .filter((num) => num > 0); + + logger.debug( + `Found ${existingSubtechniqueNumbers.length} existing subtechniques for ${parentTechniqueAttackId}`, + ); + + // Get next available subtechnique number + const nextNum = + existingSubtechniqueNumbers.length > 0 ? Math.max(...existingSubtechniqueNumbers) + 1 : 1; + + // Construct new subtechnique ID (e.g., "T1234.001") + const generatedId = `${parentTechniqueAttackId}.${nextNum.toString().padStart(3, '0')}`; + + logger.debug(`Generated subtechnique ID: ${generatedId}`); + + return generatedId; + } + + // Regular (non-subtechnique) ID generation + const attackIdType = stixTypeToAttackIdMapping[stixType]; + const typePrefix = getTypePrefix(attackIdType); + + logger.debug(`Generating ATT&CK ID for STIX type: ${stixType}, prefix: ${typePrefix}`); + + // Get all existing objects of this type + // Repository returns: [{ totalCount: [...], documents: [...] }] + const results = await repository.retrieveAll({}); + const allObjects = results[0]?.documents || []; + + logger.debug(`Retrieved ${allObjects.length} objects from repository`); + + // Extract numeric IDs from workspace.attack_id that match our type prefix + const existingIds = allObjects + .map((obj) => { + const attackId = obj.workspace?.attack_id; + if (!attackId || !attackId.startsWith(typePrefix)) { + return null; + } + + // Remove prefix and any decimal parts (for subtechniques) + const idWithoutPrefix = attackId.replace(typePrefix, '').replace(/\.(\d{3})$/, ''); + + const numericPart = parseInt(idWithoutPrefix, 10); + return isNaN(numericPart) ? null : numericPart; + }) + .filter((id) => id !== null); + + logger.debug(`Found ${existingIds.length} existing IDs with prefix ${typePrefix}`); + + // Calculate next available ID (start at 1 if none exist) + const nextId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1; + + // Construct new ID with proper padding (e.g., "G0042", "TA0001", "T1234") + const generatedId = `${typePrefix}${nextId.toString().padStart(4, '0')}`; + + logger.debug(`Generated ATT&CK ID: ${generatedId}`); + + return generatedId; +} +exports.generateAttackId = generateAttackId; + +/** + * Check if ATT&CK ID is present in workspace/STIX object and validate it + * + * @param {object} data - The object data with workspace and stix properties + * @param {string} stixType - The STIX type of the object + * @param {object} repository - The repository for querying existing objects + * @returns {Promise} True if ID is present and valid, false if not present + * @throws {Error} If ID is present but invalid or if workspace.attack_id conflicts with external_id + * + * @example + * // No ID present - returns false + * const hasId = await attackIdInWorkspaceStixObject({ stix: {}, workspace: {} }, 'x-mitre-tactic', repository); + * // Returns: false + * + * @example + * // Valid ID present - returns true + * const hasId = await attackIdInWorkspaceStixObject( + * { stix: {}, workspace: { attack_id: 'TA9999' } }, + * 'x-mitre-tactic', + * repository + * ); + * // Returns: true (if TA9999 is valid and available) + * + * @example + * // Invalid ID present - throws error + * const hasId = await attackIdInWorkspaceStixObject( + * { stix: {}, workspace: { attack_id: 'INVALID' } }, + * 'x-mitre-tactic', + * repository + * ); + * // Throws: Error (invalid format) + */ +async function hasValidAttackId(data, stixType, repository) { + const workspaceAttackId = data.workspace?.attack_id; + const externalId = extractAttackIdFromExternalReferences(data.stix); + + // No ATT&CK ID present in workspace + if (!workspaceAttackId) { + return false; + } + + // ATT&CK ID present - validate it + // First check if both workspace and external IDs are present + if (externalId && workspaceAttackId !== externalId) { + throw new Error( + `Conflicting ATT&CK IDs: workspace.attack_id (${workspaceAttackId}) does not match external_references[0].external_id (${externalId})`, + ); + } + + // Validate format and availability + await validateAttackId(workspaceAttackId, stixType, repository); + + return true; +} +exports.hasValidAttackId = hasValidAttackId; diff --git a/app/routes/attack-objects-routes.js b/app/routes/attack-objects-routes.js index f9117488..91b04a62 100644 --- a/app/routes/attack-objects-routes.js +++ b/app/routes/attack-objects-routes.js @@ -16,4 +16,12 @@ router attackObjectsController.retrieveAll, ); +router + .route('/attack-objects/attack-id/next') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + attackObjectsController.getNextAttackId, + ); + module.exports = router; diff --git a/app/services/_base.service.js b/app/services/_base.service.js index f3d8890e..09166c3c 100644 --- a/app/services/_base.service.js +++ b/app/services/_base.service.js @@ -3,6 +3,7 @@ const uuid = require('uuid'); const logger = require('../lib/logger'); const config = require('../config/config'); +const attackIdGenerator = require('../lib/attack-id-generator'); const { DatabaseError, IdentityServiceError, @@ -314,6 +315,29 @@ class BaseService extends AbstractService { options = options || {}; if (!options.import) { + // Generate or validate ATT&CK ID + const hasValidId = await attackIdGenerator.hasValidAttackId(data, this.type, this.repository); + + if (!hasValidId) { + // No ID present, generate one + // Note: Only supports regular ID generation, not subtechniques + // For subtechniques, the client must provide the ATT&CK ID explicitly + const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; + if (isSubtechnique) { + throw new Error( + 'Subtechniques require an explicit ATT&CK ID in workspace.attack_id. Use the /api/attack-objects/attack-id/next endpoint with parentRef to preview the ID.', + ); + } + + const attackId = await attackIdGenerator.generateAttackId( + this.type, + this.repository, + false, + null, + ); + data.workspace = data.workspace || {}; + data.workspace.attack_id = attackId; + } // Set the ATT&CK Spec Version data.stix.x_mitre_attack_spec_version = data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; diff --git a/app/services/attack-objects-service.js b/app/services/attack-objects-service.js index a2df0ccc..cb1efb1e 100644 --- a/app/services/attack-objects-service.js +++ b/app/services/attack-objects-service.js @@ -194,6 +194,67 @@ class AttackObjectsService extends BaseService { throw new DatabaseError(err); } } + + /** + * Get the next available ATT&CK ID for a given STIX type + * @param {string} stixType - The STIX type (e.g., 'x-mitre-tactic', 'attack-pattern') + * @param {string} parentRef - Optional parent technique STIX ID (for subtechniques) + * @returns {Promise} The next available ATT&CK ID, or null if type doesn't support IDs + */ + async getNextAttackId(stixType, parentRef = null) { + const attackIdGenerator = require('../lib/attack-id-generator'); + + // Map STIX types to their repositories + const repositoryMap = { + 'x-mitre-tactic': require('../repository/tactics-repository'), + 'attack-pattern': require('../repository/techniques-repository'), + 'intrusion-set': require('../repository/groups-repository'), + malware: require('../repository/software-repository'), + tool: require('../repository/software-repository'), + 'course-of-action': require('../repository/mitigations-repository'), + 'x-mitre-data-source': require('../repository/data-sources-repository'), + 'x-mitre-data-component': require('../repository/data-components-repository'), + 'x-mitre-asset': require('../repository/assets-repository'), + campaign: require('../repository/campaigns-repository'), + 'x-mitre-detection-strategy': require('../repository/detection-strategies-repository'), + 'x-mitre-analytic': require('../repository/analytics-repository'), + }; + + const repository = repositoryMap[stixType]; + if (!repository) { + throw new Error(`No repository found for STIX type: ${stixType}`); + } + + // Handle subtechnique ID generation + if (parentRef) { + if (stixType !== 'attack-pattern') { + throw new Error('Parent reference is only valid for attack-pattern type'); + } + + // Get parent technique to extract its ATT&CK ID + const techniquesService = require('./techniques-service'); + const parentTechniques = await techniquesService.retrieveById(parentRef, { + versions: 'latest', + }); + + if (!parentTechniques || parentTechniques.length === 0) { + throw new Error(`Parent technique not found: ${parentRef}`); + } + + const parentTechnique = parentTechniques[0]; + const parentAttackId = parentTechnique.workspace?.attack_id; + + if (!parentAttackId) { + throw new Error('Parent technique does not have an ATT&CK ID'); + } + + // Generate subtechnique ID + return await attackIdGenerator.generateAttackId(stixType, repository, true, parentAttackId); + } + + // Regular ID generation + return await attackIdGenerator.generateAttackId(stixType, repository, false); + } } module.exports.AttackObjectsService = AttackObjectsService; diff --git a/app/tests/api/attack-objects/attack-objects-attack-id.spec.js b/app/tests/api/attack-objects/attack-objects-attack-id.spec.js new file mode 100644 index 00000000..df0fe6d6 --- /dev/null +++ b/app/tests/api/attack-objects/attack-objects-attack-id.spec.js @@ -0,0 +1,287 @@ +const request = require('supertest'); +const { expect } = require('expect'); + +const database = require('../../../lib/database-in-memory'); +const databaseConfiguration = require('../../../lib/database-configuration'); +const AttackObject = require('../../../models/attack-object-model'); +const config = require('../../../config/config'); +const login = require('../../shared/login'); +const tacticsService = require('../../../services/tactics-service'); +const techniquesService = require('../../../services/techniques-service'); +const groupsService = require('../../../services/groups-service'); + +const logger = require('../../../lib/logger'); +logger.level = 'debug'; + +describe('ATT&CK ID Generation API', function () { + let app; + let passportCookie; + + before(async function () { + // Establish the database connection + await database.initializeConnection(); + + // Wait until the indexes are created + await AttackObject.init(); + + // Check for a valid database configuration + await databaseConfiguration.checkSystemConfiguration(); + + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + + // Initialize the express app + app = await require('../../../index').initializeApp(); + + // Log into the app + passportCookie = await login.loginAnonymous(app); + }); + + describe('GET /api/attack-objects/attack-id/next', function () { + it('should return 400 when type parameter is missing', async function () { + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(400); + + // OpenAPI validator catches this before controller + expect(res.text).toContain("must have required property 'type'"); + }); + + it('should return 400 for unsupported STIX type', async function () { + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'marking-definition' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(400); + + // OpenAPI validator validates enum values + expect(res.text).toContain('must be equal to one of the allowed values'); + }); + + it('should generate TA0001 for first tactic when database is empty', async function () { + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'x-mitre-tactic' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body).toHaveProperty('attack_id'); + expect(res.body.attack_id).toBe('TA0001'); + }); + + it('should generate T0001 for first technique when database is empty', async function () { + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'attack-pattern' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body).toHaveProperty('attack_id'); + expect(res.body.attack_id).toBe('T0001'); + }); + + it('should generate G0001 for first group when database is empty', async function () { + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'intrusion-set' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body).toHaveProperty('attack_id'); + expect(res.body.attack_id).toBe('G0001'); + }); + + it('should generate sequential IDs after creating objects', async function () { + // Create a tactic with TA0001 + const tactic = { + workspace: { + attack_id: 'TA0001', + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Tactic', + type: 'x-mitre-tactic', + spec_version: '2.1', + x_mitre_shortname: 'test-tactic', + description: 'A test tactic', + created: '2023-01-01T00:00:00.000Z', + modified: '2023-01-01T00:00:00.000Z', + x_mitre_domains: ['enterprise-attack'], + }, + }; + await tacticsService.create(tactic); + + // Request next tactic ID + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'x-mitre-tactic' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body.attack_id).toBe('TA0002'); + }); + + it('should handle gaps in ID sequence correctly', async function () { + // Create tactics with TA0002 and TA0005 (skipping TA0003 and TA0004) + const tactic2 = { + workspace: { + attack_id: 'TA0002', + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Tactic 2', + type: 'x-mitre-tactic', + spec_version: '2.1', + x_mitre_shortname: 'test-tactic-2', + description: 'A test tactic 2', + created: '2023-01-02T00:00:00.000Z', + modified: '2023-01-02T00:00:00.000Z', + x_mitre_domains: ['enterprise-attack'], + }, + }; + await tacticsService.create(tactic2); + + const tactic5 = { + workspace: { + attack_id: 'TA0005', + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Tactic 5', + type: 'x-mitre-tactic', + spec_version: '2.1', + x_mitre_shortname: 'test-tactic-5', + description: 'A test tactic 5', + created: '2023-01-05T00:00:00.000Z', + modified: '2023-01-05T00:00:00.000Z', + x_mitre_domains: ['enterprise-attack'], + }, + }; + await tacticsService.create(tactic5); + + // Next ID should be TA0006 (max + 1) + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'x-mitre-tactic' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body.attack_id).toBe('TA0006'); + }); + + it('should handle multiple types independently', async function () { + // Create a technique with T0001 + const technique = { + workspace: { + attack_id: 'T0001', + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Technique', + type: 'attack-pattern', + spec_version: '2.1', + description: 'A test technique', + created: '2023-01-01T00:00:00.000Z', + modified: '2023-01-01T00:00:00.000Z', + x_mitre_domains: ['enterprise-attack'], + x_mitre_is_subtechnique: false, + }, + }; + await techniquesService.create(technique); + + // Create a group with G0001 + const group = { + workspace: { + attack_id: 'G0001', + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Group', + type: 'intrusion-set', + spec_version: '2.1', + description: 'A test group', + created: '2023-01-01T00:00:00.000Z', + modified: '2023-01-01T00:00:00.000Z', + }, + }; + await groupsService.create(group); + + // Check that technique counter is T0002 + const techRes = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'attack-pattern' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + expect(techRes.body.attack_id).toBe('T0002'); + + // Check that group counter is G0002 + const groupRes = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'intrusion-set' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + expect(groupRes.body.attack_id).toBe('G0002'); + + // Check that tactic counter is still TA0006 (from previous test) + const tacticRes = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: 'x-mitre-tactic' }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + expect(tacticRes.body.attack_id).toBe('TA0006'); + }); + + it('should generate IDs for all supported STIX types', async function () { + const typeToExpectedPrefix = { + 'x-mitre-tactic': 'TA', + 'attack-pattern': 'T', + 'intrusion-set': 'G', + malware: 'S', + tool: 'S', + 'course-of-action': 'M', + 'x-mitre-data-source': 'DS', + 'x-mitre-data-component': 'DC', + 'x-mitre-asset': 'A', + campaign: 'C', + 'x-mitre-detection-strategy': 'DET', + 'x-mitre-analytic': 'AN', + }; + + for (const [stixType, prefix] of Object.entries(typeToExpectedPrefix)) { + const res = await request(app) + .get('/api/attack-objects/attack-id/next') + .query({ type: stixType }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + expect(res.body.attack_id).toMatch(new RegExp(`^${prefix}`)); + logger.debug(`${stixType} -> ${res.body.attack_id}`); + } + }); + }); + + after(async function () { + await database.closeConnection(); + }); +}); From c21573450b9bc317bcb32384e476d834532a6c2d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:43:46 -0400 Subject: [PATCH 041/370] style: apply formatting --- app/api/definitions/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 10b0ea9e..0eace35f 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -71,7 +71,7 @@ paths: # ATT&CK Objects /api/attack-objects: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects' - + # Generate next available ATT&CK ID /api/attack-objects/attack-id/next: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects~1attack-id~1next' From fba46528f57afdda36af62cd0fa733080deed8d3 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:52:23 -0400 Subject: [PATCH 042/370] test: remove references to code in unmerged adm branch --- .../api/attack-objects/attack-objects-attack-id.spec.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/tests/api/attack-objects/attack-objects-attack-id.spec.js b/app/tests/api/attack-objects/attack-objects-attack-id.spec.js index df0fe6d6..0117fd07 100644 --- a/app/tests/api/attack-objects/attack-objects-attack-id.spec.js +++ b/app/tests/api/attack-objects/attack-objects-attack-id.spec.js @@ -4,7 +4,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const AttackObject = require('../../../models/attack-object-model'); -const config = require('../../../config/config'); +// const config = require('../../../config/config'); const login = require('../../shared/login'); const tacticsService = require('../../../services/tactics-service'); const techniquesService = require('../../../services/techniques-service'); @@ -28,8 +28,9 @@ describe('ATT&CK ID Generation API', function () { await databaseConfiguration.checkSystemConfiguration(); // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; - config.validateRequests.withOpenApi = true; + // TODO reenable after 'adm' branch merge + // config.validateRequests.withAttackDataModel = false; + // config.validateRequests.withOpenApi = true; // Initialize the express app app = await require('../../../index').initializeApp(); From 66937ef5657668b686b6b8450080351f349b46f9 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:56:07 -0400 Subject: [PATCH 043/370] fix: add attack-data-model to project dependencies --- package-lock.json | 1228 ++++----------------------------------------- package.json | 1 + 2 files changed, 101 insertions(+), 1128 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63aa9e61..ec583f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", + "@mitre-attack/attack-data-model": "^4.5.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", @@ -81,1072 +82,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true, - "peer": true - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true, - "peer": true - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true, - "peer": true - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true, - "peer": true - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true, - "peer": true - }, - "node_modules/@aws-sdk/abort-controller": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.303.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-sdk-sts": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "fast-xml-parser": "4.1.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/config-resolver": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-config-provider": "3.295.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-ini": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/client-sso": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/token-providers": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.303.0", - "@aws-sdk/client-sso": "3.303.0", - "@aws-sdk/client-sts": "3.303.0", - "@aws-sdk/credential-provider-cognito-identity": "3.303.0", - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-ini": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/querystring-builder": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/hash-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-buffer-from": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/service-error-classification": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "tslib": "^2.5.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-serde": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/signature-v4": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-config-provider": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/abort-controller": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/querystring-builder": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/property-provider": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/protocol-http": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-builder": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-uri-escape": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-parser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/service-error-classification": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-hex-encoding": "3.295.0", - "@aws-sdk/util-middleware": "3.303.0", - "@aws-sdk/util-uri-escape": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/url-parser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/querystring-parser": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-base64": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-config-provider": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-middleware": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-retry": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/service-error-classification": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, "node_modules/@babel/code-frame": { "version": "7.26.2", "dev": true, @@ -2009,6 +944,30 @@ "version": "7.1.3", "license": "MIT" }, + "node_modules/@mitre-attack/attack-data-model": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.1.tgz", + "integrity": "sha512-0b5Q+67SzsfHjAWYVCL8AQ5qlNa+poLDhfWt4n7vYQKQwyqJr6BEU7x+mcQpj4V/+B0qmJiBWx7uBeb0h778rQ==", + "license": "APACHE-2.0", + "dependencies": { + "axios": "^1.9.0", + "uuid": "^10.0.0", + "zod": "^4.0.5" + } + }, + "node_modules/@mitre-attack/attack-data-model/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.9", "license": "MIT", @@ -2082,6 +1041,7 @@ "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.1.2", @@ -2841,7 +1801,8 @@ }, "node_modules/@types/node": { "version": "14.11.8", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -2926,6 +1887,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2997,6 +1959,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3129,6 +2092,17 @@ "version": "0.4.0", "license": "MIT" }, + "node_modules/axios": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", + "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -3231,12 +2205,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bowser": { - "version": "2.11.0", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4024,6 +2992,7 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -4520,6 +3489,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "dev": true, @@ -4553,6 +3537,7 @@ "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4614,6 +3599,7 @@ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4988,6 +3974,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5349,22 +4336,6 @@ "version": "3.0.3", "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.1.2", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -5615,7 +4586,6 @@ "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, "funding": [ { "type": "individual", @@ -5648,11 +4618,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -6109,6 +5083,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -6460,12 +5449,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ip": { - "version": "2.0.0", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/ipaddr.js": { "version": "1.9.1", "license": "MIT", @@ -7254,6 +6237,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7728,6 +6712,7 @@ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.3", @@ -11626,6 +10611,7 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11699,6 +10685,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "license": "MIT", @@ -12029,6 +11021,7 @@ "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -12546,16 +11539,6 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/snyk": { "version": "1.1297.1", "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.1297.1.tgz", @@ -12574,20 +11557,6 @@ "node": ">=12" } }, - "node_modules/socks": { - "version": "2.7.1", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12805,12 +11774,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/super-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", @@ -13198,7 +12161,7 @@ }, "node_modules/tslib": { "version": "2.8.1", - "devOptional": true, + "dev": true, "license": "0BSD" }, "node_modules/type-check": { @@ -13776,6 +12739,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 2799addb..7a9655fe 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", + "@mitre-attack/attack-data-model": "^4.5.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.0", From ba5119ce0a19581a1beee75185e9d046ec1e6aed Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:57:00 -0400 Subject: [PATCH 044/370] test: remove unnecessary console log --- app/tests/api/groups/groups.query.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 7819a2e6..1c3aecb6 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -283,7 +283,6 @@ describe('Groups API Queries', function () { // We expect to get the latest group with the correct ATT&CK ID const groups = res.body; logger.info(`Received groups: ${groups}`); - console.log(`Received groups: ${JSON.stringify(groups)}`); expect(groups).toBeDefined(); expect(Array.isArray(groups)).toBe(true); expect(groups.length).toBe(1); From 8c9ac601d92e735f9fb4d312dcaa77bce26c662d Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:57:34 -0400 Subject: [PATCH 045/370] refactor: move requiresAttackId function from stix-bundle-service to lib/attack-id-generator --- app/lib/attack-id-generator.js | 12 +++++++++++- app/services/stix-bundles-service.js | 24 ++---------------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js index 5034d99b..f87e4899 100644 --- a/app/lib/attack-id-generator.js +++ b/app/lib/attack-id-generator.js @@ -4,10 +4,20 @@ const { stixTypeToAttackIdMapping, attackIdExamples, createAttackIdSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/common/attack-id'); +} = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-id'); const { InvalidTypeError, DuplicateIdError } = require('../exceptions'); const logger = require('./logger'); +/** + * Determines if a given ATT&CK object type requires an ATT&CK ID. + * @param {string} objectType - The ATT&CK object type to check + * @returns {boolean} True if the object type requires an ATT&CK ID + */ +function requiresAttackId(objectType) { + return objectType in Object.keys(stixTypeToAttackIdMapping); +} +exports.requiresAttackId = requiresAttackId; + /** * Get the type prefix for an ATT&CK ID type * @param {string} attackIdType - The ATT&CK ID type (e.g., 'technique', 'tactic') diff --git a/app/services/stix-bundles-service.js b/app/services/stix-bundles-service.js index 8cdf587c..cca24054 100644 --- a/app/services/stix-bundles-service.js +++ b/app/services/stix-bundles-service.js @@ -5,6 +5,7 @@ const config = require('../config/config'); const BaseService = require('./_base.service'); const linkById = require('../lib/linkById'); const logger = require('../lib/logger'); +const { requiresAttackId } = require('../lib/attack-id-generator'); // Import repositories const analyticsRepository = require('../repository/analytics-repository'); @@ -213,27 +214,6 @@ class StixBundlesService extends BaseService { return false; } - /** - * Determines if a given attack object type requires an ATT&CK ID. - * @param {Object} attackObject - The attack object to check - * @returns {boolean} True if the object type requires an ATT&CK ID - */ - static requiresAttackId(attackObject) { - const attackIdObjectTypes = [ - 'intrusion-set', - 'campaign', - 'malware', - 'tool', - 'attack-pattern', - 'course-of-action', - 'x-mitre-data-source', - 'x-mitre-data-component', - 'x-mitre-detection-strategy', - 'x-mitre-analytic', - ]; - return attackIdObjectTypes.includes(attackObject?.stix?.type); - } - // ============================ // STIX Version Management // ============================ @@ -370,7 +350,7 @@ class StixBundlesService extends BaseService { secondaryObject && // Check if ATT&CK ID is required (options.includeMissingAttackId || - !StixBundlesService.requiresAttackId(secondaryObject) || + !requiresAttackId(secondaryObject?.stix?.type) || StixBundlesService.hasAttackId(secondaryObject)) && // Check deprecation status (options.includeDeprecated || !secondaryObject.stix.x_mitre_deprecated) && From aa4e76291a24e4e8595e33240b5422c73ded8bc7 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:58:23 -0400 Subject: [PATCH 046/370] fix(base-service): only verify attack IDs on objects that actually need it --- app/services/_base.service.js | 44 ++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/app/services/_base.service.js b/app/services/_base.service.js index 09166c3c..5a2c9987 100644 --- a/app/services/_base.service.js +++ b/app/services/_base.service.js @@ -315,28 +315,34 @@ class BaseService extends AbstractService { options = options || {}; if (!options.import) { - // Generate or validate ATT&CK ID - const hasValidId = await attackIdGenerator.hasValidAttackId(data, this.type, this.repository); - - if (!hasValidId) { - // No ID present, generate one - // Note: Only supports regular ID generation, not subtechniques - // For subtechniques, the client must provide the ATT&CK ID explicitly - const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; - if (isSubtechnique) { - throw new Error( - 'Subtechniques require an explicit ATT&CK ID in workspace.attack_id. Use the /api/attack-objects/attack-id/next endpoint with parentRef to preview the ID.', - ); - } - - const attackId = await attackIdGenerator.generateAttackId( + if (attackIdGenerator.requiresAttackId(this.type)) { + // Generate or validate ATT&CK ID + const hasValidId = await attackIdGenerator.hasValidAttackId( + data, this.type, this.repository, - false, - null, ); - data.workspace = data.workspace || {}; - data.workspace.attack_id = attackId; + + if (!hasValidId) { + // No ID present, generate one + // Note: Only supports regular ID generation, not subtechniques + // For subtechniques, the client must provide the ATT&CK ID explicitly + const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; + if (isSubtechnique) { + throw new Error( + 'Subtechniques require an explicit ATT&CK ID in workspace.attack_id. Use the /api/attack-objects/attack-id/next endpoint with parentRef to preview the ID.', + ); + } + + const attackId = await attackIdGenerator.generateAttackId( + this.type, + this.repository, + false, + null, + ); + data.workspace = data.workspace || {}; + data.workspace.attack_id = attackId; + } } // Set the ATT&CK Spec Version data.stix.x_mitre_attack_spec_version = From a3b27f2afde4777594c5d8b87c2c055a204f0cc8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:15:32 -0400 Subject: [PATCH 047/370] feat(scheduler): implement node-schedule and add WIP ATT&CK ID monitoring task Implement a robust task scheduling system using the node-schedule library to replace the existing setInterval-based scheduler. This provides better control over task scheduling with cron-like patterns. Add a new scheduled task that monitors work-in-progress objects with assigned ATT&CK IDs. This helps prevent premature exhaustion of the limited ATT&CK ID pool (0001-9999 per type) by identifying WIP objects that may never be published. Changes: - Add node-schedule dependency for cron-style task scheduling - Create app/scheduler/index.js to dynamically load and schedule tasks - Implement check-wip-attack-ids-task.js that queries repositories for WIP objects with ATT&CK IDs - Add CHECK_WIP_ATTACK_IDS_INTERVAL config (default: 1 hour) - Update bin/www to initialize new scheduler alongside existing collection manager - Tasks follow convention: *-task.js files must export a scheduleMe function --- app/config/config.js | 5 + app/scheduler/check-wip-attack-ids-task.js | 168 +++++++++++++++++++++ app/scheduler/index.js | 54 +++++++ bin/www | 12 +- package-lock.json | 62 ++++++-- package.json | 1 + 6 files changed, 287 insertions(+), 15 deletions(-) create mode 100644 app/scheduler/check-wip-attack-ids-task.js create mode 100644 app/scheduler/index.js diff --git a/app/config/config.js b/app/config/config.js index 0345d229..8da85797 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -229,6 +229,11 @@ function loadConfig() { default: 10, env: 'CHECK_WORKBENCH_INTERVAL', }, + checkWipAttackIdsInterval: { + doc: 'Sets the interval in seconds for checking WIP objects with ATT&CK IDs.', + default: 3600, + env: 'CHECK_WIP_ATTACK_IDS_INTERVAL', + }, enableScheduler: { format: Boolean, default: true, diff --git a/app/scheduler/check-wip-attack-ids-task.js b/app/scheduler/check-wip-attack-ids-task.js new file mode 100644 index 00000000..5bdcde4a --- /dev/null +++ b/app/scheduler/check-wip-attack-ids-task.js @@ -0,0 +1,168 @@ +'use strict'; + +const logger = require('../lib/logger'); +const config = require('../config/config'); + +// Import all repository types that can have ATT&CK IDs +const techniquesRepository = require('../repository/techniques-repository'); +const tacticsRepository = require('../repository/tactics-repository'); +const groupsRepository = require('../repository/groups-repository'); +const softwareRepository = require('../repository/software-repository'); +const mitigationsRepository = require('../repository/mitigations-repository'); +const campaignsRepository = require('../repository/campaigns-repository'); +const dataSourcesRepository = require('../repository/data-sources-repository'); +const dataComponentsRepository = require('../repository/data-components-repository'); + +// Map of STIX types to their repositories +const STIX_TYPE_TO_REPOSITORY = { + 'attack-pattern': { repo: techniquesRepository, name: 'techniques' }, + 'x-mitre-tactic': { repo: tacticsRepository, name: 'tactics' }, + 'intrusion-set': { repo: groupsRepository, name: 'groups' }, + malware: { repo: softwareRepository, name: 'software (malware)' }, + tool: { repo: softwareRepository, name: 'software (tool)' }, + 'course-of-action': { repo: mitigationsRepository, name: 'mitigations' }, + campaign: { repo: campaignsRepository, name: 'campaigns' }, + 'x-mitre-data-source': { repo: dataSourcesRepository, name: 'data-sources' }, + 'x-mitre-data-component': { repo: dataComponentsRepository, name: 'data-components' }, +}; + +/** + * Check for work-in-progress objects that have ATT&CK IDs assigned + * + * This task identifies objects that are in "work-in-progress" state but have + * ATT&CK IDs assigned. This is a concern because: + * 1. WIP objects may never be published, wasting limited ATT&CK ID space + * 2. The numeric range for ATT&CK IDs is limited (0001-9999 per type) + * 3. Automatic ID assignment could exhaust the pool more quickly + * + * @returns {Promise} Summary of findings + */ +async function checkWipAttackIds() { + logger.info('[check-wip-attack-ids] Starting check for WIP objects with ATT&CK IDs'); + + const results = { + timestamp: new Date().toISOString(), + totalWipWithIds: 0, + byType: {}, + objects: [], + }; + + try { + // Check each repository type + for (const [, { repo, name }] of Object.entries(STIX_TYPE_TO_REPOSITORY)) { + logger.debug(`[check-wip-attack-ids] Checking ${name} objects`); + + // Query for objects in work-in-progress state + // Use retrieveAll with state filter + const queryResult = await repo.retrieveAll({ + state: 'work-in-progress', + includeRevoked: true, + includeDeprecated: true, + offset: 0, + limit: 0, // Get all results + }); + + // Extract documents from the result structure + const allWipObjects = queryResult[0]?.documents || []; + + // Filter to only those with attack_id set + const wipObjectsWithIds = allWipObjects.filter( + (obj) => obj.workspace?.attack_id && obj.workspace.attack_id !== null, + ); + + if (wipObjectsWithIds.length > 0) { + logger.warn( + `[check-wip-attack-ids] Found ${wipObjectsWithIds.length} WIP ${name} object(s) with ATT&CK IDs`, + ); + + results.byType[name] = wipObjectsWithIds.length; + results.totalWipWithIds += wipObjectsWithIds.length; + + // Add details for each object + for (const obj of wipObjectsWithIds) { + results.objects.push({ + stixId: obj.stix.id, + stixType: obj.stix.type, + attackId: obj.workspace.attack_id, + name: obj.stix.name || '(unnamed)', + modified: obj.stix.modified, + }); + } + } + } + + if (results.totalWipWithIds > 0) { + logger.warn( + `[check-wip-attack-ids] ALERT: ${results.totalWipWithIds} work-in-progress object(s) have ATT&CK IDs assigned`, + ); + logger.warn( + `[check-wip-attack-ids] This may indicate premature ID exhaustion. Consider reviewing these objects.`, + ); + + // Log summary by type + for (const [type, count] of Object.entries(results.byType)) { + logger.warn(`[check-wip-attack-ids] - ${type}: ${count}`); + } + } else { + logger.info('[check-wip-attack-ids] No WIP objects with ATT&CK IDs found'); + } + } catch (err) { + logger.error(`[check-wip-attack-ids] Error during check: ${err.message}`); + logger.error(err.stack); + throw err; + } + + logger.info('[check-wip-attack-ids] Check complete'); + return results; +} + +/** + * Schedule this task using node-schedule + * + * @param {object} schedule - The node-schedule module + */ +async function scheduleMe(schedule) { + // Get interval from config (defaults to 1 hour if not specified) + const intervalSeconds = config.scheduler.checkWipAttackIdsInterval || 3600; // 1 hour default + + logger.info( + `[check-wip-attack-ids] Scheduling task to run every ${intervalSeconds} seconds (${Math.floor(intervalSeconds / 60)} minutes)`, + ); + + // Schedule the job to run at the specified interval + // If interval is >= 60 seconds, use minute-based scheduling + if (intervalSeconds >= 60) { + const intervalMinutes = Math.floor(intervalSeconds / 60); + const minuteRule = new schedule.RecurrenceRule(); + minuteRule.minute = new schedule.Range(0, 59, intervalMinutes); + minuteRule.second = 0; + + schedule.scheduleJob(minuteRule, async () => { + try { + await checkWipAttackIds(); + } catch (err) { + logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); + } + }); + } else { + // For intervals < 60 seconds, use a simpler cron pattern + const rule = new schedule.RecurrenceRule(); + rule.second = new schedule.Range(0, 59, Math.max(10, intervalSeconds)); + + schedule.scheduleJob(rule, async () => { + try { + await checkWipAttackIds(); + } catch (err) { + logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); + } + }); + } + + logger.info('[check-wip-attack-ids] Task scheduled successfully'); +} + +// Export the scheduleMe function (required by the scheduler) +module.exports = { + scheduleMe, + checkWipAttackIds, // Export for testing +}; diff --git a/app/scheduler/index.js b/app/scheduler/index.js new file mode 100644 index 00000000..039bf6e5 --- /dev/null +++ b/app/scheduler/index.js @@ -0,0 +1,54 @@ +'use strict'; + +const schedule = require('node-schedule'); +const logger = require('../lib/logger'); +const config = require('../config/config'); +const fs = require('fs'); +const path = require('path'); + +/** + * Initialize the scheduler by loading all task modules and scheduling them + */ +async function initializeScheduler() { + if (!config.scheduler.enableScheduler) { + logger.info('Scheduler is disabled by configuration'); + return; + } + + logger.info('Initializing node-schedule based scheduler'); + + // Find all *-task.js files in the scheduler directory + const schedulerDir = __dirname; + const taskFiles = fs.readdirSync(schedulerDir).filter((file) => file.endsWith('-task.js')); + + logger.info(`Found ${taskFiles.length} task file(s) to load`); + + // Load and schedule each task + for (const taskFile of taskFiles) { + const taskPath = path.join(schedulerDir, taskFile); + logger.info(`Loading task from ${taskFile}`); + + try { + const taskModule = require(taskPath); + + // Validate that the module exports a scheduleMe function + if (typeof taskModule.scheduleMe !== 'function') { + logger.error(`Task file ${taskFile} does not export a 'scheduleMe' function. Skipping.`); + continue; + } + + // Call scheduleMe to schedule the task + await taskModule.scheduleMe(schedule); + logger.info(`Successfully scheduled task from ${taskFile}`); + } catch (err) { + logger.error(`Failed to load or schedule task from ${taskFile}: ${err.message}`); + logger.error(err.stack); + } + } + + logger.info('Scheduler initialization complete'); +} + +module.exports = { + initializeScheduler, +}; diff --git a/bin/www b/bin/www index c90edb78..f4f2faaa 100644 --- a/bin/www +++ b/bin/www @@ -51,8 +51,16 @@ async function runServer() { const app = await require('../app').initializeApp(); // Create the scheduler - const scheduler = require('../app/scheduler/scheduler'); - scheduler.initializeScheduler(); + // TODO migrate to new task scheduling paradigm + const collectionManager = require('../app/scheduler/scheduler'); + collectionManager.initializeScheduler(); + + // NEW scheduler (uses the Node Schedule library to schedule tasks) + // To schedule jobs: + // 1. Create a new JS file in `../app/scheduler` and set "-task" suffix in the filename, e.g, foo-task.js + // 2. In the new JS file, export a function called `scheduleMe` -- this function is what gets scheduled! + const scheduler = require('../app/scheduler'); + await scheduler.initializeScheduler(); // Start the server logger.info('Starting the HTTP server...'); diff --git a/package-lock.json b/package-lock.json index ec583f50..9fab422b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "morgan": "^1.10.1", "nanoid": "^5.1.5", "node-cache": "^5.1.2", + "node-schedule": "^2.1.1", "openid-client": "^5.7.1", "passport": "^0.7.0", "passport-anonym-uuid": "^1.0.3", @@ -1041,7 +1042,6 @@ "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.1.2", @@ -1801,8 +1801,7 @@ }, "node_modules/@types/node": { "version": "14.11.8", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -1887,7 +1886,6 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1959,7 +1957,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2992,7 +2989,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -3032,6 +3028,18 @@ "typescript": ">=5" } }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -3537,7 +3545,6 @@ "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3599,7 +3606,6 @@ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3974,7 +3980,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -6194,6 +6199,12 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "license": "MIT" + }, "node_modules/lru_map": { "version": "0.3.3", "dev": true, @@ -6217,6 +6228,15 @@ "lru-cache": "6.0.0" } }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "4.0.0", "dev": true, @@ -6237,7 +6257,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -6712,7 +6731,6 @@ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.3", @@ -7100,6 +7118,20 @@ "node": ">=18" } }, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "license": "MIT", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -10611,7 +10643,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11021,7 +11052,6 @@ "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -11557,6 +11587,12 @@ "node": ">=12" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 7a9655fe..9cdd8bb3 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "morgan": "^1.10.1", "nanoid": "^5.1.5", "node-cache": "^5.1.2", + "node-schedule": "^2.1.1", "openid-client": "^5.7.1", "passport": "^0.7.0", "passport-anonym-uuid": "^1.0.3", From 95dbab678a2cedcb5024f6a71b109459424b7b9c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:18:35 -0400 Subject: [PATCH 048/370] refactor(logger): use conditional formatting based on log level Switch to one-liner log format for normal operation and detailed prettyPrint format only when LOG_LEVEL is set to debug or verbose. This provides cleaner logs in production while maintaining detailed output for debugging. --- app/lib/logger.js | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/app/lib/logger.js b/app/lib/logger.js index 81f565e7..8597c750 100644 --- a/app/lib/logger.js +++ b/app/lib/logger.js @@ -3,33 +3,6 @@ const winston = require('winston'); const config = require('../config/config'); -// function formatId(info) { -// if (info.level.toUpperCase() === 'HTTP') { -// return ''; -// } -// else if (info.id) { -// return `[${ info.id }] `; -// } -// else { -// return '[ 000000000000 ] '; -// } -// } - -// NOTE if you want to enable one-liner logs, use this instead: -// const consoleFormat = winston.format.combine( -// winston.format.timestamp(), -// winston.format.printf( -// (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, -// ), -// // winston.format.printf(info => `${ info.timestamp } [${ info.level.toUpperCase() }] ${ formatId(info) }${ info.message }`) -// ); - -const consoleFormat = winston.format.combine( - winston.format.timestamp(), - winston.format.errors({ stack: true }), - winston.format.prettyPrint(), -); - const logLevels = { error: 0, warn: 1, @@ -39,6 +12,22 @@ const logLevels = { debug: 5, }; +// Use detailed format for debug/verbose levels, cleaner one-liner format otherwise +const consoleFormat = + config.logging.logLevel === 'debug' || config.logging.logLevel === 'verbose' + ? winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.prettyPrint(), + ) + : winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.printf( + (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, + ), + ); + const logger = winston.createLogger({ format: consoleFormat, transports: [new winston.transports.Console({ level: config.logging.logLevel })], From a07a3d32439f74b6c0ec9dfd5998ef4b14c4afae Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:51:13 -0400 Subject: [PATCH 049/370] refactor(scheduler): simplify node-schedule impl logic --- app/config/config.js | 8 +-- app/scheduler/check-wip-attack-ids-task.js | 63 ++++++++-------------- app/scheduler/index.js | 27 +++------- bin/www | 23 ++++---- 4 files changed, 44 insertions(+), 77 deletions(-) diff --git a/app/config/config.js b/app/config/config.js index 8da85797..6b7b3d51 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -229,10 +229,10 @@ function loadConfig() { default: 10, env: 'CHECK_WORKBENCH_INTERVAL', }, - checkWipAttackIdsInterval: { - doc: 'Sets the interval in seconds for checking WIP objects with ATT&CK IDs.', - default: 3600, - env: 'CHECK_WIP_ATTACK_IDS_INTERVAL', + checkWipAttackIdsCron: { + doc: 'Cron pattern for checking WIP objects with ATT&CK IDs (e.g., "0 * * * *" for hourly).', + default: '0 * * * *', + env: 'CHECK_WIP_ATTACK_IDS_CRON', }, enableScheduler: { format: Boolean, diff --git a/app/scheduler/check-wip-attack-ids-task.js b/app/scheduler/check-wip-attack-ids-task.js index 5bdcde4a..6bf525ba 100644 --- a/app/scheduler/check-wip-attack-ids-task.js +++ b/app/scheduler/check-wip-attack-ids-task.js @@ -1,5 +1,6 @@ 'use strict'; +const schedule = require('node-schedule'); const logger = require('../lib/logger'); const config = require('../config/config'); @@ -117,52 +118,30 @@ async function checkWipAttackIds() { } /** - * Schedule this task using node-schedule - * - * @param {object} schedule - The node-schedule module + * Initialize and schedule this task */ -async function scheduleMe(schedule) { - // Get interval from config (defaults to 1 hour if not specified) - const intervalSeconds = config.scheduler.checkWipAttackIdsInterval || 3600; // 1 hour default - - logger.info( - `[check-wip-attack-ids] Scheduling task to run every ${intervalSeconds} seconds (${Math.floor(intervalSeconds / 60)} minutes)`, - ); - - // Schedule the job to run at the specified interval - // If interval is >= 60 seconds, use minute-based scheduling - if (intervalSeconds >= 60) { - const intervalMinutes = Math.floor(intervalSeconds / 60); - const minuteRule = new schedule.RecurrenceRule(); - minuteRule.minute = new schedule.Range(0, 59, intervalMinutes); - minuteRule.second = 0; - - schedule.scheduleJob(minuteRule, async () => { - try { - await checkWipAttackIds(); - } catch (err) { - logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); - } - }); - } else { - // For intervals < 60 seconds, use a simpler cron pattern - const rule = new schedule.RecurrenceRule(); - rule.second = new schedule.Range(0, 59, Math.max(10, intervalSeconds)); - - schedule.scheduleJob(rule, async () => { - try { - await checkWipAttackIds(); - } catch (err) { - logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); - } - }); - } +function initializeTask() { + const cronPattern = config.scheduler.checkWipAttackIdsCron; + + logger.info(`[check-wip-attack-ids] Scheduling task with cron pattern: ${cronPattern}`); + + schedule.scheduleJob(cronPattern, async () => { + try { + await checkWipAttackIds(); + } catch (err) { + logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); + } + }); logger.info('[check-wip-attack-ids] Task scheduled successfully'); } -// Export the scheduleMe function (required by the scheduler) +// Initialize the task when this module is loaded +if (config.scheduler.enableScheduler) { + initializeTask(); +} + +// Export for testing module.exports = { - scheduleMe, - checkWipAttackIds, // Export for testing + checkWipAttackIds, }; diff --git a/app/scheduler/index.js b/app/scheduler/index.js index 039bf6e5..1df6ee17 100644 --- a/app/scheduler/index.js +++ b/app/scheduler/index.js @@ -1,21 +1,21 @@ 'use strict'; -const schedule = require('node-schedule'); const logger = require('../lib/logger'); const config = require('../config/config'); const fs = require('fs'); const path = require('path'); /** - * Initialize the scheduler by loading all task modules and scheduling them + * Initialize the scheduler by loading all task modules + * Task modules self-initialize when required */ -async function initializeScheduler() { +function initializeScheduler() { if (!config.scheduler.enableScheduler) { logger.info('Scheduler is disabled by configuration'); return; } - logger.info('Initializing node-schedule based scheduler'); + logger.info('Loading scheduled tasks'); // Find all *-task.js files in the scheduler directory const schedulerDir = __dirname; @@ -23,25 +23,14 @@ async function initializeScheduler() { logger.info(`Found ${taskFiles.length} task file(s) to load`); - // Load and schedule each task + // Require each task file (they self-initialize) for (const taskFile of taskFiles) { const taskPath = path.join(schedulerDir, taskFile); - logger.info(`Loading task from ${taskFile}`); - try { - const taskModule = require(taskPath); - - // Validate that the module exports a scheduleMe function - if (typeof taskModule.scheduleMe !== 'function') { - logger.error(`Task file ${taskFile} does not export a 'scheduleMe' function. Skipping.`); - continue; - } - - // Call scheduleMe to schedule the task - await taskModule.scheduleMe(schedule); - logger.info(`Successfully scheduled task from ${taskFile}`); + require(taskPath); + logger.info(`Loaded task from ${taskFile}`); } catch (err) { - logger.error(`Failed to load or schedule task from ${taskFile}: ${err.message}`); + logger.error(`Failed to load task from ${taskFile}: ${err.message}`); logger.error(err.stack); } } diff --git a/bin/www b/bin/www index f4f2faaa..901592ba 100644 --- a/bin/www +++ b/bin/www @@ -51,16 +51,13 @@ async function runServer() { const app = await require('../app').initializeApp(); // Create the scheduler - // TODO migrate to new task scheduling paradigm + // TODO migrate to taskScheduler const collectionManager = require('../app/scheduler/scheduler'); collectionManager.initializeScheduler(); - - // NEW scheduler (uses the Node Schedule library to schedule tasks) - // To schedule jobs: - // 1. Create a new JS file in `../app/scheduler` and set "-task" suffix in the filename, e.g, foo-task.js - // 2. In the new JS file, export a function called `scheduleMe` -- this function is what gets scheduled! - const scheduler = require('../app/scheduler'); - await scheduler.initializeScheduler(); + + // Load node-schedule based tasks (task files ending in -task.js) + const taskScheduler = require('../app/scheduler'); + taskScheduler.initializeScheduler(); // Start the server logger.info('Starting the HTTP server...'); @@ -84,13 +81,15 @@ async function runServer() { // Listen for a ctrl-c process.on('SIGINT', () => { logger.info('SIGINT received, stopping HTTP server'); - server.close(); + collectionManager.gracefulShutdown() + .then(() => server.close()); }); // Docker terminates a container with a SIGTERM process.on('SIGTERM', () => { logger.info('SIGTERM received, stopping HTTP server'); - server.close(); + collectionManager.gracefulShutdown() + .then(() => server.close()); }); process.on('uncaughtException', (err, origin) => { @@ -99,8 +98,8 @@ async function runServer() { logger.error(err.stack); logger.error('Terminating app after uncaught exception'); - - process.exit(1); + collectionManager.gracefulShutdown() + .then(() => process.exit(1)); }); // Wait for the server to close From 90e06b509987710490117019c6c3ac42aa90daaa Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:01:07 -0400 Subject: [PATCH 050/370] refactor(scheduler): graceful shutdown on SIGINT/SIGTERM to cleanly terminate scheduled tasks --- app/scheduler/index.js | 12 ++++++++++++ bin/www | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/scheduler/index.js b/app/scheduler/index.js index 1df6ee17..74253dfa 100644 --- a/app/scheduler/index.js +++ b/app/scheduler/index.js @@ -1,5 +1,6 @@ 'use strict'; +const schedule = require('node-schedule'); const logger = require('../lib/logger'); const config = require('../config/config'); const fs = require('fs'); @@ -38,6 +39,17 @@ function initializeScheduler() { logger.info('Scheduler initialization complete'); } +/** + * Gracefully shutdown all scheduled jobs + * @returns {Promise} Promise that resolves when all jobs are terminated + */ +async function gracefulShutdown() { + logger.info('Gracefully shutting down scheduled tasks'); + await schedule.gracefulShutdown(); + logger.info('All scheduled tasks have been shut down'); +} + module.exports = { initializeScheduler, + gracefulShutdown, }; diff --git a/bin/www b/bin/www index 901592ba..e845baa0 100644 --- a/bin/www +++ b/bin/www @@ -81,14 +81,14 @@ async function runServer() { // Listen for a ctrl-c process.on('SIGINT', () => { logger.info('SIGINT received, stopping HTTP server'); - collectionManager.gracefulShutdown() + taskScheduler.gracefulShutdown() .then(() => server.close()); }); // Docker terminates a container with a SIGTERM process.on('SIGTERM', () => { logger.info('SIGTERM received, stopping HTTP server'); - collectionManager.gracefulShutdown() + taskScheduler.gracefulShutdown() .then(() => server.close()); }); @@ -98,7 +98,7 @@ async function runServer() { logger.error(err.stack); logger.error('Terminating app after uncaught exception'); - collectionManager.gracefulShutdown() + taskScheduler.gracefulShutdown() .then(() => process.exit(1)); }); From d357109f9889ee87e0bd70b91c9c9bce70927730 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:13:28 -0400 Subject: [PATCH 051/370] refactor(scheduler): migrate former collection mgr to new scheduling paradigm - Renamed from scheduler.js to sync-collection-indexes-task.js - Prefixed all log statements with [sync-collection-indexes] - Moved from millisecond multiplier to cron pattern for config/env variable - Changed default from every 10 seconds to every minute --- app/config/config.js | 8 +- app/scheduler/index.js | 1 + ...ler.js => sync-collection-indexes-task.js} | 90 ++++++++++++------- bin/www | 5 -- 4 files changed, 63 insertions(+), 41 deletions(-) rename app/scheduler/{scheduler.js => sync-collection-indexes-task.js} (71%) diff --git a/app/config/config.js b/app/config/config.js index 6b7b3d51..d0d7aa79 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -224,14 +224,14 @@ function loadConfig() { }, }, scheduler: { - checkWorkbenchInterval: { + syncCollectionIndexesCron: { doc: 'Sets the interval in seconds for starting the scheduler.', - default: 10, - env: 'CHECK_WORKBENCH_INTERVAL', + default: '* * * * *', // every minute + env: 'SYNC_COLLECTION_INDEXES_CRON', }, checkWipAttackIdsCron: { doc: 'Cron pattern for checking WIP objects with ATT&CK IDs (e.g., "0 * * * *" for hourly).', - default: '0 * * * *', + default: '0 * * * *', // every hour env: 'CHECK_WIP_ATTACK_IDS_CRON', }, enableScheduler: { diff --git a/app/scheduler/index.js b/app/scheduler/index.js index 74253dfa..545a48f7 100644 --- a/app/scheduler/index.js +++ b/app/scheduler/index.js @@ -28,6 +28,7 @@ function initializeScheduler() { for (const taskFile of taskFiles) { const taskPath = path.join(schedulerDir, taskFile); try { + // Loading the module will trigger its `initializeTask` method which schedules the task require(taskPath); logger.info(`Loaded task from ${taskFile}`); } catch (err) { diff --git a/app/scheduler/scheduler.js b/app/scheduler/sync-collection-indexes-task.js similarity index 71% rename from app/scheduler/scheduler.js rename to app/scheduler/sync-collection-indexes-task.js index 2cb12cbe..4aad97a4 100644 --- a/app/scheduler/scheduler.js +++ b/app/scheduler/sync-collection-indexes-task.js @@ -1,5 +1,6 @@ 'use strict'; +const schedule = require('node-schedule'); const collectionIndexesService = require('../services/collection-indexes-service'); const collectionsService = require('../services/collections-service'); const collectionBundlesService = require('../services/collection-bundles-service'); @@ -17,20 +18,6 @@ const config = require('../config/config'); const superagent = require('superagent'); -let timer; -exports.initializeScheduler = function () { - logger.info('Starting the scheduler'); - - const intervalMilliseconds = config.scheduler.checkWorkbenchInterval * 1000; - timer = setInterval(runCheckCollectionIndexes, intervalMilliseconds); -}; - -exports.stopScheduler = function () { - if (timer) { - clearInterval(timer); - } -}; - const scheduledSubscriptions = new Map(); async function retrieveByUrl(url) { @@ -58,13 +45,13 @@ async function retrieveByUrl(url) { const runCheckCollectionIndexes = async function () { const updatedCollections = []; - logger.info('Scheduler running...'); + logger.info('[sync-collection-indexes] Scheduler running...'); let collectionIndexes; try { collectionIndexes = await collectionIndexesService.retrieveAll({ offset: 0, limit: 0 }); } catch (err) { - logger.error('Unable to get existing collection indexes: ' + err); + logger.error('[sync-collection-indexes] Unable to get existing collection indexes: ' + err); } for (const collectionIndex of collectionIndexes) { @@ -80,24 +67,27 @@ const runCheckCollectionIndexes = async function () { now - lastRetrieval > 1000 * collectionIndex.workspace.update_policy.interval ) { logger.info( - `Checking collection index: ${collectionIndex.collection_index.name} (${collectionIndex.collection_index.id})`, + `[sync-collection-indexes] Checking collection index: ${collectionIndex.collection_index.name} (${collectionIndex.collection_index.id})`, ); logger.verbose( - 'Retrieving collection index from remote url ' + collectionIndex.workspace.remote_url, + '[sync-collection-indexes] Retrieving collection index from remote url ' + + collectionIndex.workspace.remote_url, ); let remoteCollectionIndex; try { remoteCollectionIndex = await retrieveByUrl(collectionIndex.workspace.remote_url); } catch (err) { - logger.error('Unable to retrieve collection index from remote url. ' + err); + logger.error( + '[sync-collection-indexes] Unable to retrieve collection index from remote url. ' + err, + ); } const remoteTimestamp = new Date(remoteCollectionIndex.modified); const existingTimestamp = new Date(collectionIndex.collection_index.modified); if (remoteTimestamp > existingTimestamp) { logger.info( - 'The retrieved collection index is newer. Updating collection index in workbench.', + '[sync-collection-indexes] The retrieved collection index is newer. Updating collection index in workbench.', ); collectionIndex.collection_index = remoteCollectionIndex; collectionIndex.workspace.update_policy.last_retrieval = new Date(now).toISOString(); @@ -109,18 +99,20 @@ const runCheckCollectionIndexes = async function () { collectionIndex, ); } catch (err) { - logger.error('Unable to update collection index in workbench. ' + err); + logger.error( + '[sync-collection-indexes] Unable to update collection index in workbench. ' + err, + ); return updatedCollections; } // Check subscribed collections if (scheduledSubscriptions.has(savedCollectionIndex.collection_index.id)) { logger.info( - `Subscriptions for collection index ${savedCollectionIndex.collection_index.id} are already being checked`, + `[sync-collection-indexes] Subscriptions for collection index ${savedCollectionIndex.collection_index.id} are already being checked`, ); } else { logger.verbose( - `Checking Subscriptions for collection index ${savedCollectionIndex.collection_index.id}`, + `[sync-collection-indexes] Checking Subscriptions for collection index ${savedCollectionIndex.collection_index.id}`, ); scheduledSubscriptions.set(savedCollectionIndex.collection_index.id, true); try { @@ -128,12 +120,14 @@ const runCheckCollectionIndexes = async function () { updatedCollections.push(collectionIndex.collection_index.id); scheduledSubscriptions.delete(savedCollectionIndex.collection_index.id); } catch (err) { - logger.error('Error checking subscriptions in collection index. ' + err); + logger.error( + '[sync-collection-indexes] Error checking subscriptions in collection index. ' + err, + ); return updatedCollections; } } } else { - logger.verbose('The retrieved collection index is not newer.'); + logger.verbose('[sync-collection-indexes] The retrieved collection index is not newer.'); collectionIndex.workspace.update_policy.last_retrieval = new Date(now).toISOString(); try { await collectionIndexesService.updateFull( @@ -141,18 +135,20 @@ const runCheckCollectionIndexes = async function () { collectionIndex, ); } catch (err) { - logger.error('Unable to update collection index in workbench. ' + err); + logger.error( + '[sync-collection-indexes] Unable to update collection index in workbench. ' + err, + ); return updatedCollections; } // Check subscribed collections if (scheduledSubscriptions.has(collectionIndex.collection_index.id)) { logger.info( - `Subscriptions for collection index ${collectionIndex.collection_index.id} are already being checked`, + `[sync-collection-indexes] Subscriptions for collection index ${collectionIndex.collection_index.id} are already being checked`, ); } else { logger.info( - `Checking Subscriptions for collection index ${collectionIndex.collection_index.id}`, + `[sync-collection-indexes] Checking Subscriptions for collection index ${collectionIndex.collection_index.id}`, ); scheduledSubscriptions.set(collectionIndex.collection_index.id, true); try { @@ -160,7 +156,10 @@ const runCheckCollectionIndexes = async function () { updatedCollections.push(collectionIndex.collection_index.id); scheduledSubscriptions.delete(collectionIndex.collection_index.id); } catch (err) { - logger.error('Error checking subscriptions in collection index. ' + err); + logger.error( + '[sync-collection-indexes] Error checking subscriptions in collection index. ' + + err, + ); return updatedCollections; } } @@ -197,7 +196,9 @@ async function subscriptionHandler(collectionIndex) { ) { // Latest version in collection index is later than latest version in the Workbench data store, // so we should import it - logger.info(`Retrieving collection bundle from remote url ${collectionInfo.versions[0].url}`); + logger.info( + `[sync-collection-indexes] Retrieving collection bundle from remote url ${collectionInfo.versions[0].url}`, + ); let collectionBundle; try { @@ -205,7 +206,9 @@ async function subscriptionHandler(collectionIndex) { } catch (err) { throw new Error('Unable to retrieve updated collection bundle. ' + err); } - logger.info(`Downloaded updated collection bundle with id ${collectionBundle.id}`); + logger.info( + `[sync-collection-indexes] Downloaded updated collection bundle with id ${collectionBundle.id}`, + ); // Find the x-mitre-collection objects const collections = collectionBundle.objects.filter( @@ -241,7 +244,7 @@ async function subscriptionHandler(collectionIndex) { importOptions, ); logger.info( - `Imported collection bundle with x-mitre-collection id ${importedCollection.stix.id}`, + `[sync-collection-indexes] Imported collection bundle with x-mitre-collection id ${importedCollection.stix.id}`, ); } catch (err) { throw new Error( @@ -251,3 +254,26 @@ async function subscriptionHandler(collectionIndex) { } } } + +/** + * Initialize and schedule this task + */ +function initializeTask() { + const cronPattern = config.scheduler.syncCollectionIndexesCron; + + logger.info(`[sync-collection-indexes] Scheduling task with cron pattern: ${cronPattern}`); + + schedule.scheduleJob(cronPattern, async () => { + try { + await runCheckCollectionIndexes(); + } catch (err) { + logger.error(`[sync-collection-indexes] Task execution failed: ${err.message}`); + } + }); + + logger.info(`[sync-collection-indexes] Task scheduled successfully`); +} + +if (config.scheduler.enableScheduler) { + initializeTask(); +} diff --git a/bin/www b/bin/www index e845baa0..dd16592e 100644 --- a/bin/www +++ b/bin/www @@ -51,11 +51,6 @@ async function runServer() { const app = await require('../app').initializeApp(); // Create the scheduler - // TODO migrate to taskScheduler - const collectionManager = require('../app/scheduler/scheduler'); - collectionManager.initializeScheduler(); - - // Load node-schedule based tasks (task files ending in -task.js) const taskScheduler = require('../app/scheduler'); taskScheduler.initializeScheduler(); From 3ed98bd36c7b529a0250f467ebd7d5de222dd1f0 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:13:58 -0400 Subject: [PATCH 052/370] style: apply formatting --- app/scheduler/sync-collection-indexes-task.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scheduler/sync-collection-indexes-task.js b/app/scheduler/sync-collection-indexes-task.js index 4aad97a4..dabe6909 100644 --- a/app/scheduler/sync-collection-indexes-task.js +++ b/app/scheduler/sync-collection-indexes-task.js @@ -121,7 +121,8 @@ const runCheckCollectionIndexes = async function () { scheduledSubscriptions.delete(savedCollectionIndex.collection_index.id); } catch (err) { logger.error( - '[sync-collection-indexes] Error checking subscriptions in collection index. ' + err, + '[sync-collection-indexes] Error checking subscriptions in collection index. ' + + err, ); return updatedCollections; } From 96311e7c8c208efd2276451e2f1fd8f2f3348727 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:46:29 -0400 Subject: [PATCH 053/370] docs: update USAGE.md with updated task scheduling info --- USAGE.md | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/USAGE.md b/USAGE.md index 9f04e4f5..b581dca6 100644 --- a/USAGE.md +++ b/USAGE.md @@ -17,6 +17,9 @@ This guide provides comprehensive instructions for installing, configuring, and - [Configuration](#configuration) - [Environment Variables](#environment-variables) - [Configuration File](#configuration-file) + - [Task Scheduling](#task-scheduling) + - [Collection Indexes Synchronization Task](#collection-indexes-synchronization-task) + - [ATT\&CK ID Reservation Monitoring Task](#attck-id-reservation-monitoring-task) - [Authentication](#authentication) - [Authentication Mechanisms](#authentication-mechanisms) - [OpenID Connect (OIDC) Configuration](#openid-connect-oidc-configuration) @@ -126,7 +129,9 @@ The REST API can be configured using environment variables, a configuration file | **NODE_ENV** | No | `development` | Environment that the app is running in | | **DATABASE_URL** | Yes | none | URL of the MongoDB server | | **AUTHN_MECHANISM** | No | `anonymous` | Mechanism to use for authenticating users | -| **DEFAULT_INTERVAL** | No | `300` | How often collection indexes should check for updates (in seconds) | +| **ENABLE_SCHEDULER** | No | `true` | Binary toggle for whether the task scheduler should run or not (see [Task Scheduling](#task-scheduling) for details) | +| **SYNC_COLLECTION_INDEXES_CRON** | No | `* * * * *` (every minute) | How often the [Collection Indexes Synchronization Task](#collection-indexes-synchronization-task) task should run (cron pattern) | +| **CHECK_WIP_ATTACK_IDS_CRON** | No | `0 * * * *` (every hour) | How often the [ATT&CK ID Reservation Monitoring Task](#attck-id-reservation-monitoring-task) task should run (cron pattern) | | **JSON_CONFIG_PATH** | No | `` | Location of a JSON file containing configuration values | | **LOG_LEVEL** | No | `info` | Level of messages to be written to the log (error, warn, http, info, verbose, debug) | | **WB_REST_STATIC_MARKING_DEFS_PATH** | No | `./app/lib/default-static-marking-definitions/` | Path to a directory containing static marking definitions | @@ -143,8 +148,10 @@ If the `JSON_CONFIG_PATH` environment variable is set, the app will read configu | **server.corsAllowedOrigins** | string/array | CORS_ALLOWED_ORIGINS | | **app.env** | string | NODE_ENV | | **database.url** | string | DATABASE_URL | -| **collectionIndex.defaultInterval** | int | DEFAULT_INTERVAL | | **logging.logLevel** | string | LOG_LEVEL | +| **scheduler.enableScheduler** | boolean | ENABLE_SCHEDULER | +| **scheduler.syncCollectionIndexesCron** | string | SYNC_COLLECTION_INDEXES_CRON | +| **scheduler.checkWipAttackIdsCron** | string | CHECK_WIP_ATTACK_IDS_CRON | Example configuration file: @@ -159,10 +166,104 @@ Example configuration file: }, "logging": { "logLevel": "debug" + }, + "scheduler": { + "enableScheduler": true, + "syncCollectionIndexesCron": "* * * * *", + "checkWipAttackIdsCron": "0 * * * *" } } ``` +## Task Scheduling + +The REST API uses [Node Schedule](https://github.com/node-schedule/node-schedule#readme) to schedule and run periodic tasks. + +It uses a specific design pattern: + +- Tasks are organized in isolated JS modules within the `app/scheduler/` directory. +- JS Modules must contain the suffix, `-task`. e.g., `foobar-task.js` +- JS Modules must schedule their own jobs and execute them in the global scope. For example: + ```javascript + function initializeTask() { + const cronPattern = config.scheduler.checkWipAttackIdsCron; + + logger.info(`[check-wip-attack-ids] Scheduling task with cron pattern: ${cronPattern}`); + + schedule.scheduleJob(cronPattern, async () => { + try { + await checkWipAttackIds(); + } catch (err) { + logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); + } + }); + + logger.info('[check-wip-attack-ids] Task scheduled successfully'); + } + + // Initialize the task when this module is loaded + if (config.scheduler.enableScheduler) { + initializeTask(); + } + ``` +- All JS Modules containing schedulable tasks are dynamically loaded by `app/scheduler/index.js`. ONLY modules with the `-task` suffix are loaded! + +To date, there are two tasks that are configured to automatically execute at application runtime: + +- [Collection Indexes Synchronization Task](#collection-indexes-synchronization-task) (formerly the "collection manager") +- [ATT&CK ID Reservation Monitoring Task](#attck-id-reservation-monitoring-task) + +### Collection Indexes Synchronization Task + +* **File:** [app/scheduler/sync-collection-indexes-task.js](app/scheduler/sync-collection-indexes-task.js) +* **Default Schedule:** `* * * * *` (every minute) +* **Configuration:** `SYNC_COLLECTION_INDEXES_CRON` environment variable + +This task manages the automatic synchronization of collection indexes and their subscribed collections from remote sources. It performs the following operations: + +1. **Retrieves all collection indexes** from the local database that have automatic update policies enabled +2. **Checks if updates are needed** based on the configured interval since the last retrieval +3. **Fetches remote collection indexes** from their configured URLs and compares timestamps +4. **Updates local collection indexes** when newer versions are found remotely +5. **Processes subscriptions** by checking each subscribed collection in the collection index: + - Compares local collection versions with available remote versions + - Downloads and imports newer collection bundles automatically + - Validates that bundles contain proper `x-mitre-collection` objects + +This task ensures that Workbench stays synchronized with upstream ATT&CK data sources without manual intervention, making it easier to keep local collections current. + +### ATT&CK ID Reservation Monitoring Task + +* **File:** [app/scheduler/check-wip-attack-ids-task.js](app/scheduler/check-wip-attack-ids-task.js) +* **Default Schedule:** `0 * * * *` (every hour at minute 0) +* **Configuration:** `CHECK_WIP_ATTACK_IDS_CRON` environment variable + +This task monitors for potential ATT&CK ID exhaustion issues by identifying work-in-progress (WIP) objects that have been assigned ATT&CK IDs. It performs the following operations: + +1. **Scans all STIX object types** that can have ATT&CK IDs assigned: + - Techniques (`attack-pattern`) + - Tactics (`x-mitre-tactic`) + - Groups (`intrusion-set`) + - Software (`malware` and `tool`) + - Mitigations (`course-of-action`) + - Campaigns (`campaign`) + - Data Sources (`x-mitre-data-source`) + - Data Components (`x-mitre-data-component`) + +2. **Identifies WIP objects with assigned IDs** by querying for objects in `work-in-progress` state that have an `attack_id` value + +3. **Logs warnings** when such objects are found, as this may indicate: + - Premature consumption of the limited ATT&CK ID namespace (IDs range from 0001-9999 per type) + - Objects that may never be published, permanently reserving their IDs + - Potential issues with automatic ID assignment workflows + +4. **Generates a summary report** containing: + - Total count of WIP objects with IDs + - Breakdown by object type + - Details of each affected object (STIX ID, ATT&CK ID, name, modification date) + +This monitoring helps administrators identify and review objects that may be tying up valuable ID space unnecessarily. + ## Authentication The REST API supports different authentication mechanisms for both user and service authentication. From a2767b621d24b35267dbe3cfe26643195c8edf05 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Tue, 4 Nov 2025 11:30:59 -0600 Subject: [PATCH 054/370] docs: clean up contributor guide --- CONTRIBUTING.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b104267..317e0ec3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,20 +60,20 @@ If your changes are related to or dependent on changes in [attack-workbench-fron The project uses the following branch structure to support semantic-release: -- `main` / `master`: Production-ready code +- `main`: Production-ready code - `next`: Features for the next minor version - `next-major`: Features for the next major version - `beta`: Beta pre-releases - `alpha`: Alpha pre-releases -- `*.*.x` or `*.x`: Maintenance branches for specific version releases +- `*.x`: Maintenance branches for specific version releases -Always target your pull requests to the `develop` branch unless specifically advised otherwise. +Always target your pull requests to the `main` branch unless specifically advised otherwise. ## Commit Message Guidelines This project uses [conventional commits](https://www.conventionalcommits.org/) to automatically determine semantic versioning through semantic-release. Your commit messages should follow this format: -``` +```text (): [optional body] @@ -101,7 +101,7 @@ Adding `BREAKING CHANGE:` in the commit message footer will trigger a MAJOR vers The project uses GitHub Actions for continuous integration with the following workflow: 1. **Commit Linting**: Ensures all commits follow the conventional commit format -2. **Static Checks**: +2. **Static Checks**: - Runs linting checks - Performs security scanning with Snyk - Generates code coverage reports @@ -140,11 +140,12 @@ Pre-release branches (alpha, beta) will generate pre-release versions with appro The project publishes Docker images to the GitHub Container Registry (ghcr.io) with these tags: -- `latest`: Points to the most recent release from the main branch -- `v{major}.{minor}.{patch}`: Specific version tags (e.g., `v1.2.3`) -- `{major}.{minor}.{patch}`: Version tags without the 'v' prefix -- `sha-{short-commit-sha}`: Specific commit reference +- `latest`: Points to the most recent release from the `main` branch +- `next`: Points to the most recent release from the `next` branch +- `beta`: Points to the most recent release from the `beta` branch +- `alpha`: Points to the most recent release from the `alpha` branch +- `{major}.{minor}.{patch}`: Specific version tags (e.g., `v1.2.3`) Docker images include metadata such as version, build time, and commit reference, which are accessible via both environment variables and image labels. -The image contains the Express.js REST API service and is designed to work with a MongoDB database. \ No newline at end of file +The image contains the Express.js REST API service and is designed to work with a MongoDB database. From 9127bce662215e17b2b05ae7797195898f6aef2a Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Wed, 5 Nov 2025 09:25:33 -0600 Subject: [PATCH 055/370] docs: fix markdownlint issues and format markdown tables --- USAGE.md | 95 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/USAGE.md b/USAGE.md index 9f04e4f5..a3db17c4 100644 --- a/USAGE.md +++ b/USAGE.md @@ -37,6 +37,7 @@ This guide provides comprehensive instructions for installing, configuring, and The ATT&CK Workbench REST API provides services for storing, querying, and editing ATT&CK objects. It is built on Node.js and Express.js, and uses MongoDB for data persistence. This component is part of the larger ATT&CK Workbench application, which includes: + - [ATT&CK Workbench Frontend](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) - [ATT&CK Workbench REST API](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api) (this component) @@ -55,11 +56,13 @@ The simplest way to deploy the entire ATT&CK Workbench application is using Dock To run only the REST API in a Docker container: 1. **Create a Docker network** (if not already created): + ```shell docker network create attack-workbench-network ``` 2. **Run MongoDB container**: + ```shell docker run --name attack-workbench-mongodb -d \ --network attack-workbench-network \ @@ -67,6 +70,7 @@ To run only the REST API in a Docker container: ``` 3. **Run REST API container**: + ```shell docker run -p 3000:3000 -d \ --name attack-workbench-rest-api \ @@ -96,12 +100,14 @@ docker run -p 3000:3000 -d \ #### Installation Steps 1. **Clone the repository**: + ```shell git clone https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api.git cd attack-workbench-rest-api ``` 2. **Install dependencies**: + ```shell npm install ``` @@ -109,6 +115,7 @@ docker run -p 3000:3000 -d \ 3. **Configure the application** using environment variables or a configuration file (see [Configuration](#configuration)). 4. **Start the application**: + ```shell node ./bin/www ``` @@ -119,17 +126,17 @@ The REST API can be configured using environment variables, a configuration file ### Environment Variables -| Variable | Required | Default | Description | -|----------|----------|---------|-------------| -| **PORT** | No | `3000` | Port the HTTP server should listen on | -| **CORS_ALLOWED_ORIGINS** | No | `*` | Configures CORS policy. Accepts a comma-separated list of allowed domains. (`*` allows all domains; `disable` disables CORS entirely.) | -| **NODE_ENV** | No | `development` | Environment that the app is running in | -| **DATABASE_URL** | Yes | none | URL of the MongoDB server | -| **AUTHN_MECHANISM** | No | `anonymous` | Mechanism to use for authenticating users | -| **DEFAULT_INTERVAL** | No | `300` | How often collection indexes should check for updates (in seconds) | -| **JSON_CONFIG_PATH** | No | `` | Location of a JSON file containing configuration values | -| **LOG_LEVEL** | No | `info` | Level of messages to be written to the log (error, warn, http, info, verbose, debug) | -| **WB_REST_STATIC_MARKING_DEFS_PATH** | No | `./app/lib/default-static-marking-definitions/` | Path to a directory containing static marking definitions | +| Variable | Required | Default | Description | +|--------------------------------------|----------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| +| **PORT** | No | `3000` | Port the HTTP server should listen on | +| **CORS_ALLOWED_ORIGINS** | No | `*` | Configures CORS policy. Accepts a comma-separated list of allowed domains. (`*` allows all domains; `disable` disables CORS entirely.) | +| **NODE_ENV** | No | `development` | Environment that the app is running in | +| **DATABASE_URL** | Yes | none | URL of the MongoDB server | +| **AUTHN_MECHANISM** | No | `anonymous` | Mechanism to use for authenticating users | +| **DEFAULT_INTERVAL** | No | `300` | How often collection indexes should check for updates (in seconds) | +| **JSON_CONFIG_PATH** | No | `` | Location of a JSON file containing configuration values | +| **LOG_LEVEL** | No | `info` | Level of messages to be written to the log (error, warn, http, info, verbose, debug) | +| **WB_REST_STATIC_MARKING_DEFS_PATH** | No | `./app/lib/default-static-marking-definitions/` | Path to a directory containing static marking definitions | A typical value for DATABASE_URL when running locally is `mongodb://localhost/attack-workspace`. @@ -137,14 +144,14 @@ A typical value for DATABASE_URL when running locally is `mongodb://localhost/at If the `JSON_CONFIG_PATH` environment variable is set, the app will read configuration settings from a JSON file at that location. -| Property | Type | Corresponding Environment Variable | -|----------|------|-----------------------------------| -| **server.port** | int | PORT | -| **server.corsAllowedOrigins** | string/array | CORS_ALLOWED_ORIGINS | -| **app.env** | string | NODE_ENV | -| **database.url** | string | DATABASE_URL | -| **collectionIndex.defaultInterval** | int | DEFAULT_INTERVAL | -| **logging.logLevel** | string | LOG_LEVEL | +| Property | Type | Corresponding Environment Variable | +|-------------------------------------|--------------|------------------------------------| +| **server.port** | int | PORT | +| **server.corsAllowedOrigins** | string/array | CORS_ALLOWED_ORIGINS | +| **app.env** | string | NODE_ENV | +| **database.url** | string | DATABASE_URL | +| **collectionIndex.defaultInterval** | int | DEFAULT_INTERVAL | +| **logging.logLevel** | string | LOG_LEVEL | Example configuration file: @@ -186,13 +193,13 @@ To enable OIDC authentication: 2. **Configure the REST API** with these environment variables: -| Environment Variable | Required | Description | Configuration Property | -|---------------------|----------|-------------|------------------------| -| **AUTHN_MECHANISM** | Yes | Must be set to `oidc` | userAuthn.mechanism | -| **AUTHN_OIDC_CLIENT_ID** | Yes | Client ID from your OIDC provider | userAuthn.oidc.clientId | -| **AUTHN_OIDC_CLIENT_SECRET** | Yes | Client secret from your OIDC provider | userAuthn.oidc.clientSecret | -| **AUTHN_OIDC_ISSUER_URL** | Yes | Issuer URL for the Identity Server | userAuthn.oidc.issuerUrl | -| **AUTHN_OIDC_REDIRECT_ORIGIN** | Yes | URL for the Workbench host | userAuthn.oidc.redirectOrigin | +| Environment Variable | Required | Description | Configuration Property | +|--------------------------------|----------|---------------------------------------|-------------------------------| +| **AUTHN_MECHANISM** | Yes | Must be set to `oidc` | userAuthn.mechanism | +| **AUTHN_OIDC_CLIENT_ID** | Yes | Client ID from your OIDC provider | userAuthn.oidc.clientId | +| **AUTHN_OIDC_CLIENT_SECRET** | Yes | Client secret from your OIDC provider | userAuthn.oidc.clientSecret | +| **AUTHN_OIDC_ISSUER_URL** | Yes | Issuer URL for the Identity Server | userAuthn.oidc.issuerUrl | +| **AUTHN_OIDC_REDIRECT_ORIGIN** | Yes | URL for the Workbench host | userAuthn.oidc.redirectOrigin | ### Service Authentication @@ -210,29 +217,29 @@ The REST API includes a user management system when using OIDC authentication. The system supports these roles: -| Role | Description | -|------|-------------| -| `none` | No access to the system (for pending/inactive users) | -| `visitor` | Read-only access to ATT&CK objects | -| `editor` | Read and write access to ATT&CK objects | -| `admin` | Full access to all system capabilities, including user management | +| Role | Description | +|-----------|-------------------------------------------------------------------| +| `none` | No access to the system (for pending/inactive users) | +| `visitor` | Read-only access to ATT&CK objects | +| `editor` | Read and write access to ATT&CK objects | +| `admin` | Full access to all system capabilities, including user management | ### User Account Status -| Status | Description | -|--------|-------------| -| `pending` | User has registered but awaits approval | -| `active` | User is registered and approved | -| `inactive` | User is no longer active | +| Status | Description | +|------------|-----------------------------------------| +| `pending` | User has registered but awaits approval | +| `active` | User is registered and approved | +| `inactive` | User is no longer active | ### User Management Endpoints -| Endpoint | Method | Description | Authorization | -|----------|--------|-------------|--------------| -| `/api/user-accounts` | GET | List all users | Admin only | -| `/api/user-accounts/:id` | GET | Get user by ID | Admin or self | -| `/api/user-accounts/register` | POST | Register new user | Logged in, unregistered users | -| `/api/user-accounts/:id` | PUT | Update user | Admin only | +| Endpoint | Method | Description | Authorization | +|-------------------------------|--------|-------------------|-------------------------------| +| `/api/user-accounts` | GET | List all users | Admin only | +| `/api/user-accounts/:id` | GET | Get user by ID | Admin or self | +| `/api/user-accounts/register` | POST | Register new user | Logged in, unregistered users | +| `/api/user-accounts/:id` | PUT | Update user | Admin only | ## API Documentation @@ -284,4 +291,4 @@ Common issues and their solutions: 4. **Permission denied errors**: - Check the user's role and status - - Ensure the user account has the necessary permissions for the operation \ No newline at end of file + - Ensure the user account has the necessary permissions for the operation From e3a8c39bdd2bd9eff070a5c6f73f5e7f3111fba1 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Wed, 5 Nov 2025 10:06:17 -0600 Subject: [PATCH 056/370] chore: update template.env file to have information about current environment variable options --- template.env | 135 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 5 deletions(-) diff --git a/template.env b/template.env index 15463ce9..4e99a485 100644 --- a/template.env +++ b/template.env @@ -1,5 +1,130 @@ -AUTHN_MECHANISM=anonymous -DATABASE_URL=mongodb://localhost/attack-workspace -JSON_CONFIG_PATH=/some/path/to/rest-api-service-config.json -PORT=8080 -LOG_LEVEL=debug \ No newline at end of file +# Attack Workbench REST API - Environment Configuration Template +# Guidance: +# - Booleans: use true or false +# - Lists: use comma-separated values + +# Server +# PORT (int) - HTTP server port +# Default: 3000 +#PORT=3000 + +# Database (REQUIRED) +# DATABASE_URL (string) - MongoDB connection string +# Example (Docker): +#DATABASE_URL=mongodb://attack-workbench-database/attack-workspace +# Example (local): +#DATABASE_URL=mongodb://localhost:27017/attack-workspace +DATABASE_URL= + +# CORS_ALLOWED_ORIGINS (domains) - Allowed origins for REST API +# Accepts: +# * : allow any origin +# disable : disable CORS +# Comma-separated list of origins (http/https), e.g.: +# http://localhost:3000,https://example.com,https://sub.domain.org:8443 +# Supports localhost, private IPv4 (10.x, 172.16-31.x, 192.168.x), and FQDNs. +# Default: * +#CORS_ALLOWED_ORIGINS=* + +# Application +# NODE_ENV (string) - Environment name +# Options: development, production, test +# Default: development +#NODE_ENV=development + +# WB_REST_DATABASE_MIGRATION_ENABLE (bool) - Auto-run DB migrations on startup +# Default: true +#WB_REST_DATABASE_MIGRATION_ENABLE=true + +# Logging +# LOG_LEVEL (string) - Console log level +# Options: error, warn, http, info, verbose, debug +# Default: info +#LOG_LEVEL=info + +# Workbench Collection Indexes +# DEFAULT_INTERVAL (int, seconds) - Default polling interval for new indexes +# Note: does not affect existing indexes +# Default: 300 +#DEFAULT_INTERVAL=300 + +# Configuration Files +# JSON_CONFIG_PATH (string) - Path to a JSON file with additional configuration. +# Use this to provide arrays for service accounts and OIDC clients +# Default: empty (disabled) +#JSON_CONFIG_PATH= + +# ALLOWED_VALUES_PATH (string) - Path to allowed values configuration file +# Default: ./app/config/allowed-values.json +#ALLOWED_VALUES_PATH=./app/config/allowed-values.json + +# WB_REST_STATIC_MARKING_DEFS_PATH (string) - Directory of static marking definition JSON files +# Default: ./app/lib/default-static-marking-definitions/ +#WB_REST_STATIC_MARKING_DEFS_PATH=./app/lib/default-static-marking-definitions/ + +# Scheduler +# ENABLE_SCHEDULER (bool) - Enable background scheduler +# Default: true +#ENABLE_SCHEDULER=true + +# CHECK_WORKBENCH_INTERVAL (int, seconds) - Scheduler start interval +# Default: 10 +#CHECK_WORKBENCH_INTERVAL=10 + +# Session +# SESSION_SECRET (string) - Secret to sign session cookies. +# Default: securely generated at startup (changes on restart; not recommended for production). +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +#SESSION_SECRET= + +# User Authentication +# AUTHN_MECHANISM (enum) - User login mechanism +# Options: anonymous, oidc +# Default: anonymous +#AUTHN_MECHANISM=anonymous + +# OIDC settings (required if AUTHN_MECHANISM=oidc) +# AUTHN_OIDC_ISSUER_URL (string) - OIDC issuer URL (e.g., https://idp.example.com) +# Default: empty +#AUTHN_OIDC_ISSUER_URL= +# AUTHN_OIDC_CLIENT_ID (string) - OIDC client ID +# Default: empty +#AUTHN_OIDC_CLIENT_ID= +# AUTHN_OIDC_CLIENT_SECRET (string) - OIDC client secret +# Default: empty +#AUTHN_OIDC_CLIENT_SECRET= +# AUTHN_OIDC_REDIRECT_ORIGIN (string) - Origin used to build redirect URI +# Example: http://localhost:3000 -> http://localhost:3000/authn/oidc/callback +# Default: http://localhost:3000 +#AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000 + +# Service Authentication +# OIDC Client Credentials (service-to-service) +# SERVICE_ACCOUNT_OIDC_ENABLE (bool) - Enable client credentials flow +# Default: false +#SERVICE_ACCOUNT_OIDC_ENABLE=false +# JWKS_URI (string) - JWKS endpoint for IdP public keys (required if enabled) +# Default: empty +#JWKS_URI= + +# Challenge API Key (token exchange) +# WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE (bool) - Enable challenge flow +# Default: false +#WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE=false +# WB_REST_TOKEN_SIGNING_SECRET (string) - Token signing secret +# Default: securely generated at startup (changes on restart; set for production) +#WB_REST_TOKEN_SIGNING_SECRET= +# WB_REST_TOKEN_TIMEOUT (int, seconds) - Access token lifetime +# Default: 300 +#WB_REST_TOKEN_TIMEOUT=300 + +# Basic API Key (no challenge) +# WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE (bool) - Enable basic apikey auth +# Default: false +#WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE=false + +# TLS/Certificates +# NODE_EXTRA_CA_CERTS (string) - Path to additional CA certs in PEM format +# Useful when MongoDB or IdP uses a private CA +# Default: empty +#NODE_EXTRA_CA_CERTS= From 6d9521477369a4c9b256968228e0e67bae856348 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 8 Nov 2025 15:20:28 -0600 Subject: [PATCH 057/370] feat: add mongoStoreCryptoSecret for session data encryption in MongoDB --- app/config/config.js | 6 ++++++ app/index.js | 20 +++++++++++++++++--- template.env | 5 +++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/config/config.js b/app/config/config.js index 0345d229..3d369789 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -21,6 +21,7 @@ function generateSecret() { const defaultSessionSecret = generateSecret(); const defaultTokenSigningSecret = generateSecret(); +const defaultMongoStoreCryptoSecret = generateSecret(); const userAuthnMechanismValues = ['anonymous', 'oidc']; convict.addFormat(enumFormat('user-authn-mechanism', userAuthnMechanismValues, true)); @@ -241,6 +242,11 @@ function loadConfig() { default: defaultSessionSecret, env: 'SESSION_SECRET', }, + mongoStoreCryptoSecret: { + doc: 'Secret used to encrypt session data in MongoDB', + default: defaultMongoStoreCryptoSecret, + env: 'MONGOSTORE_CRYPTO_SECRET', + }, }, userAuthn: { mechanism: { diff --git a/app/index.js b/app/index.js index 8cdf73e1..6b425fc6 100644 --- a/app/index.js +++ b/app/index.js @@ -131,14 +131,28 @@ exports.initializeApp = async function () { // Configure server-side sessions const session = require('express-session'); const MongoStore = require('connect-mongo'); + const mongoose = require('mongoose'); + + // Generate unique session cookie name based on container hostname + // This ensures multiple instances on the same domain don't conflict + // which is important for local development environments with multiple instances + const os = require('os'); + const crypto = require('crypto'); + const hostname = os.hostname(); + const cookieName = (hostname && hostname !== 'localhost') + ? `connect.${crypto.createHash('sha256').update(hostname).digest('hex').substring(0, 8)}.sid` + : 'connect.sid'; + const sessionOptions = { + name: cookieName, secret: config.session.secret, resave: false, saveUninitialized: false, store: MongoStore.create({ - client: require('mongoose').connection.getClient(), - dbName: config.database.dbName, - collectionName: 'sessions', + client: mongoose.connection.getClient(), + crypto: { + secret: config.session.mongoStoreCryptoSecret + } }), }; app.use(session(sessionOptions)); diff --git a/template.env b/template.env index 4e99a485..5d23e5a7 100644 --- a/template.env +++ b/template.env @@ -77,6 +77,11 @@ DATABASE_URL= # Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" #SESSION_SECRET= +# MONGOSTORE_CRYPTO_SECRET (string) - Secret to encrypt session data in MongoDB. +# Default: securely generated at startup (changes on restart; not recommended for production). +# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +#MONGOSTORE_CRYPTO_SECRET= + # User Authentication # AUTHN_MECHANISM (enum) - User login mechanism # Options: anonymous, oidc From b4e6c006ec09fc3e996f92a4370ff00d3056046e Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sat, 8 Nov 2025 15:21:14 -0600 Subject: [PATCH 058/370] chore: run prettier --- app/index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/index.js b/app/index.js index 6b425fc6..6537e4d9 100644 --- a/app/index.js +++ b/app/index.js @@ -139,9 +139,10 @@ exports.initializeApp = async function () { const os = require('os'); const crypto = require('crypto'); const hostname = os.hostname(); - const cookieName = (hostname && hostname !== 'localhost') - ? `connect.${crypto.createHash('sha256').update(hostname).digest('hex').substring(0, 8)}.sid` - : 'connect.sid'; + const cookieName = + hostname && hostname !== 'localhost' + ? `connect.${crypto.createHash('sha256').update(hostname).digest('hex').substring(0, 8)}.sid` + : 'connect.sid'; const sessionOptions = { name: cookieName, @@ -151,8 +152,8 @@ exports.initializeApp = async function () { store: MongoStore.create({ client: mongoose.connection.getClient(), crypto: { - secret: config.session.mongoStoreCryptoSecret - } + secret: config.session.mongoStoreCryptoSecret, + }, }), }; app.use(session(sessionOptions)); From 3c9920573142a1056848b3b2e7f3eb38da1f52c3 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 08:59:23 -0600 Subject: [PATCH 059/370] test: fix regression tests to be aware of new cookie naming convention --- .../analytics/analytics-includeRefs.spec.js | 32 +++++----- app/tests/api/analytics/analytics-spec.js | 52 ++++++++-------- app/tests/api/assets/assets.spec.js | 38 ++++++------ .../api/attack-objects/attack-objects.spec.js | 20 +++---- app/tests/api/campaigns/campaigns.spec.js | 42 ++++++------- .../collection-bundles.spec.js | 60 +++++++++---------- .../collection-indexes.spec.js | 20 +++---- app/tests/api/collections/collections.spec.js | 46 +++++++------- .../data-components/data-components.spec.js | 44 +++++++------- .../api/data-sources/data-sources.spec.js | 38 ++++++------ .../detection-strategies-spec.js | 36 +++++------ .../groups/groups-input-validation.spec.js | 8 +-- app/tests/api/groups/groups.query.spec.js | 20 +++---- app/tests/api/groups/groups.spec.js | 46 +++++++------- app/tests/api/identities/identities.spec.js | 36 +++++------ .../marking-definitions.spec.js | 20 +++---- app/tests/api/matrices/matrices.spec.js | 38 ++++++------ app/tests/api/mitigations/mitigations.spec.js | 36 +++++------ app/tests/api/notes/notes.spec.js | 50 ++++++++-------- .../recent-activity/recent-activity.spec.js | 4 +- app/tests/api/references/references.spec.js | 34 +++++------ .../api/relationships/relationships.spec.js | 56 ++++++++--------- app/tests/api/software/software.spec.js | 42 ++++++------- .../api/stix-bundles/stix-bundles-old.spec.js | 14 ++--- .../api/stix-bundles/stix-bundles.spec.js | 20 +++---- .../create-object-identity.spec.js | 10 ++-- .../system-configuration.spec.js | 28 ++++----- app/tests/api/tactics/tactics.spec.js | 40 ++++++------- .../api/tactics/tactics.techniques.spec.js | 10 ++-- app/tests/api/teams/teams-invalid.spec.js | 2 +- app/tests/api/teams/teams.spec.js | 20 +++---- .../api/techniques/techniques.query.spec.js | 22 +++---- app/tests/api/techniques/techniques.spec.js | 42 ++++++------- .../api/techniques/techniques.tactics.spec.js | 12 ++-- .../user-accounts-invalid.spec.js | 2 +- .../api/user-accounts/user-accounts.spec.js | 28 ++++----- app/tests/fuzz/user-accounts-fuzz.spec.js | 8 +-- .../collection-bundles-enterprise.spec.js | 8 +-- app/tests/scheduler/scheduler.spec.js | 2 +- app/tests/shared/login.js | 4 +- app/tests/shared/pagination.js | 18 +++--- 41 files changed, 555 insertions(+), 553 deletions(-) diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index 5d5262d6..bf1390b7 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -119,7 +119,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/data-components') .send(dataComponentData) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -137,7 +137,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/analytics') .send(analyticData) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -156,7 +156,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/detection-strategies') .send(detectionStrategyData) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -171,7 +171,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get('/api/analytics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -191,7 +191,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get('/api/analytics?includeRefs=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -228,7 +228,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get('/api/analytics?includeRefs=true&includePagination=true&limit=10') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -251,7 +251,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get(`/api/analytics/${createdAnalytic.stix.id}?includeRefs=false`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -268,7 +268,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get(`/api/analytics/${createdAnalytic.stix.id}?includeRefs=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -303,7 +303,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get(`/api/analytics/${createdAnalytic.stix.id}?versions=all&includeRefs=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -338,7 +338,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/analytics') .send(analyticWithoutRefs) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); const createdAnalyticWithoutRefs = createRes.body; @@ -346,7 +346,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get(`/api/analytics/${createdAnalyticWithoutRefs.stix.id}?includeRefs=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); const analytics = res.body; @@ -383,7 +383,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/analytics') .send(analyticWithBadRef) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); const createdAnalyticWithBadRef = createRes.body; @@ -391,7 +391,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get(`/api/analytics/${createdAnalyticWithBadRef.stix.id}?includeRefs=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); const analytics = res.body; @@ -423,7 +423,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/data-components') .send(dataComponentWithoutExtRefs) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); const analyticWithNoExtRefDataComponent = { @@ -447,7 +447,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/analytics') .send(analyticWithNoExtRefDataComponent) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); const createdAnalyticWithNoExtRefDataComponent = createRes.body; @@ -455,7 +455,7 @@ describe('Analytics API - includeRefs Parameter', function () { const res = await request(app) .get(`/api/analytics/${createdAnalyticWithNoExtRefDataComponent.stix.id}?includeRefs=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); const analytics = res.body; diff --git a/app/tests/api/analytics/analytics-spec.js b/app/tests/api/analytics/analytics-spec.js index 207b4c92..ff781699 100644 --- a/app/tests/api/analytics/analytics-spec.js +++ b/app/tests/api/analytics/analytics-spec.js @@ -85,7 +85,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -102,7 +102,7 @@ describe('Analytics API', function () { .post('/api/analytics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -116,7 +116,7 @@ describe('Analytics API', function () { .post('/api/analytics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -144,7 +144,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -159,7 +159,7 @@ describe('Analytics API', function () { await request(app) .get('/api/analytics/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -167,7 +167,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics/' + analytic1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -215,7 +215,7 @@ describe('Analytics API', function () { .put('/api/analytics/' + analytic1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -232,7 +232,7 @@ describe('Analytics API', function () { .post('/api/analytics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -249,7 +249,7 @@ describe('Analytics API', function () { .post('/api/analytics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -271,7 +271,7 @@ describe('Analytics API', function () { .post('/api/analytics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -284,7 +284,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics/' + analytic3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -302,7 +302,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics/' + analytic1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -317,7 +317,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics/' + analytic1.stix.id + '/modified/' + analytic1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -333,7 +333,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics/' + analytic2.stix.id + '/modified/' + analytic2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -348,21 +348,21 @@ describe('Analytics API', function () { it('DELETE /api/analytics/:id should not delete a analytic when the id cannot be found', async function () { await request(app) .delete('/api/analytics/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/analytics/:id/modified/:modified deletes a analytic', async function () { await request(app) .delete('/api/analytics/' + analytic1.stix.id + '/modified/' + analytic1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/analytics/:id should delete all the analytics with the same stix id', async function () { await request(app) .delete('/api/analytics/' + analytic2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -402,7 +402,7 @@ describe('Analytics API', function () { .post('/api/analytics') .send(searchAnalyticData) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -421,7 +421,7 @@ describe('Analytics API', function () { .post('/api/detection-strategies') .send(detectionStrategyData) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -437,7 +437,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics?search=Search Test') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -452,7 +452,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics?search=Network Connection Creation') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -467,7 +467,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics?search=Network Connection') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -482,7 +482,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics?search=NonExistentTerm') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -497,14 +497,14 @@ describe('Analytics API', function () { if (searchTestDetectionStrategy) { await request(app) .delete('/api/detection-strategies/' + searchTestDetectionStrategy.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); } if (searchTestAnalytic) { await request(app) .delete('/api/analytics/' + searchTestAnalytic.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); } }); @@ -514,7 +514,7 @@ describe('Analytics API', function () { const res = await request(app) .get('/api/analytics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/assets/assets.spec.js b/app/tests/api/assets/assets.spec.js index 478b44b1..c5982507 100644 --- a/app/tests/api/assets/assets.spec.js +++ b/app/tests/api/assets/assets.spec.js @@ -67,7 +67,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -84,7 +84,7 @@ describe('Assets API', function () { .post('/api/assets') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -98,7 +98,7 @@ describe('Assets API', function () { .post('/api/assets') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -124,7 +124,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -139,7 +139,7 @@ describe('Assets API', function () { await request(app) .get('/api/assets/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -147,7 +147,7 @@ describe('Assets API', function () { await request(app) .get('/api/assets/not-an-id?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -155,7 +155,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets/' + asset1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -199,7 +199,7 @@ describe('Assets API', function () { .put('/api/assets/' + asset1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -216,7 +216,7 @@ describe('Assets API', function () { .post('/api/assets') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -233,7 +233,7 @@ describe('Assets API', function () { .post('/api/assets') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -255,7 +255,7 @@ describe('Assets API', function () { .post('/api/assets') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -268,7 +268,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets/' + asset3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -286,7 +286,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets/' + asset1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -301,7 +301,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets/' + asset1.stix.id + '/modified/' + asset1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -317,7 +317,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets/' + asset2.stix.id + '/modified/' + asset2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -332,21 +332,21 @@ describe('Assets API', function () { it('DELETE /api/assets/:id should not delete an asset when the id cannot be found', async function () { await request(app) .delete('/api/assets/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/assets/:id/modified/:modified deletes an asset', async function () { await request(app) .delete('/api/assets/' + asset1.stix.id + '/modified/' + asset1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/assets/:id should delete all the assets with the same stix id', async function () { await request(app) .delete('/api/assets/' + asset2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -354,7 +354,7 @@ describe('Assets API', function () { const res = await request(app) .get('/api/assets') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index 5c9ff8da..cabe52ec 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -89,7 +89,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -114,7 +114,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -139,7 +139,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?attackId=T1234') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -154,7 +154,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?attackId=GX1111') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -169,7 +169,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?attackId=SX3333') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -184,7 +184,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?attackId=TX0001') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -199,7 +199,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?attackId=GX1111&attackId=SX3333&attackId=TX0001') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -214,7 +214,7 @@ describe('ATT&CK Objects API', function () { const res = await request(app) .get('/api/attack-objects?search=nabu') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -235,7 +235,7 @@ describe('ATT&CK Objects API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -253,7 +253,7 @@ describe('ATT&CK Objects API', function () { `/api/attack-objects?lastUpdatedBy=${software1.workspace.workflow.created_by_user_account}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/campaigns/campaigns.spec.js b/app/tests/api/campaigns/campaigns.spec.js index c70970c4..6673920b 100644 --- a/app/tests/api/campaigns/campaigns.spec.js +++ b/app/tests/api/campaigns/campaigns.spec.js @@ -98,7 +98,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -115,7 +115,7 @@ describe('Campaigns API', function () { .post('/api/campaigns') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -129,7 +129,7 @@ describe('Campaigns API', function () { .post('/api/campaigns') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -161,7 +161,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -176,7 +176,7 @@ describe('Campaigns API', function () { await request(app) .get('/api/campaigns/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -184,7 +184,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns/' + campaign1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -221,7 +221,7 @@ describe('Campaigns API', function () { .put('/api/campaigns/' + campaign1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -238,7 +238,7 @@ describe('Campaigns API', function () { .post('/api/campaigns') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -262,7 +262,7 @@ describe('Campaigns API', function () { .post('/api/campaigns') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -275,7 +275,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns/' + campaign2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -304,7 +304,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns/' + campaign1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -319,7 +319,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns/' + campaign1.stix.id + '/modified/' + campaign1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -335,7 +335,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns/' + campaign2.stix.id + '/modified/' + campaign2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -364,7 +364,7 @@ describe('Campaigns API', function () { .post('/api/campaigns') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -377,7 +377,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns?search=green') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -399,7 +399,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns?search=blue') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -414,7 +414,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns?search=brown') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -435,21 +435,21 @@ describe('Campaigns API', function () { it('DELETE /api/campaigns/:id should not delete a campaign when the id cannot be found', async function () { await request(app) .delete('/api/campaigns/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/campaigns/:id/modified/:modified deletes a campaign', async function () { await request(app) .delete('/api/campaigns/' + campaign3.stix.id + '/modified/' + campaign3.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/campaigns/:id should delete all of the campaigns with the stix id', async function () { await request(app) .delete('/api/campaigns/' + campaign2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -457,7 +457,7 @@ describe('Campaigns API', function () { const res = await request(app) .get('/api/campaigns') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/collection-bundles/collection-bundles.spec.js b/app/tests/api/collection-bundles/collection-bundles.spec.js index 23351b77..f57b541b 100644 --- a/app/tests/api/collection-bundles/collection-bundles.spec.js +++ b/app/tests/api/collection-bundles/collection-bundles.spec.js @@ -455,7 +455,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -465,7 +465,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); const errorResult = response.body; @@ -478,7 +478,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); const errorResult = response.body; @@ -491,7 +491,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); const errorResult = response.body; @@ -504,7 +504,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); const errorResult = response.body; @@ -517,7 +517,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles?forceImport=attack-spec-version-violations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); }); @@ -527,7 +527,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles?checkOnly=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -545,7 +545,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles?previewOnly=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -561,7 +561,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -577,7 +577,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles?checkOnly=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -587,7 +587,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -597,7 +597,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles?forceImport=duplicate-collection') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); }); @@ -613,7 +613,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(updatedCollection) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -628,7 +628,7 @@ describe('Collection Bundles Basic API', function () { const response = await request(app) .get('/api/references?sourceName=' + encodeURIComponent('malware-1 source')) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -642,7 +642,7 @@ describe('Collection Bundles Basic API', function () { const res = await request(app) .get('/api/references?sourceName=' + encodeURIComponent('xyzzy')) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -657,7 +657,7 @@ describe('Collection Bundles Basic API', function () { const res = await request(app) .get('/api/references?sourceName=' + encodeURIComponent('group source')) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -672,7 +672,7 @@ describe('Collection Bundles Basic API', function () { const res = await request(app) .get('/api/references?sourceName=' + encodeURIComponent('group-xyzzy')) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -687,7 +687,7 @@ describe('Collection Bundles Basic API', function () { await request(app) .get('/api/collection-bundles?collectionId=not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -697,7 +697,7 @@ describe('Collection Bundles Basic API', function () { `/api/collection-bundles?previewOnly=true&collectionId=x-mitre-collection--30ee11cf-0a05-4d9e-ab54-9b8563669647`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -712,7 +712,7 @@ describe('Collection Bundles Basic API', function () { const res = await request(app) .get(`/api/collection-bundles?collectionId=${collectionId}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -730,7 +730,7 @@ describe('Collection Bundles Basic API', function () { `/api/collection-bundles?collectionId=${collectionId}&collectionModified=${encodeURIComponent(collectionTimestamp)}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -747,7 +747,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collections') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); }); @@ -755,7 +755,7 @@ describe('Collection Bundles Basic API', function () { const res = await request(app) .get(`/api/collection-bundles?collectionId=${collectionId6}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -770,7 +770,7 @@ describe('Collection Bundles Basic API', function () { const res = await request(app) .get(`/api/collection-bundles?collectionId=${collectionId6}&includeNotes=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -795,7 +795,7 @@ describe('Collection Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -835,7 +835,7 @@ describe('Collection Bundles Streaming API', function () { .post('/api/collection-bundles?stream=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); // Verify SSE headers expect(response.status).toBe(200); @@ -851,7 +851,7 @@ describe('Collection Bundles Streaming API', function () { .post('/api/collection-bundles?stream=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); // Should still return SSE headers even for errors expect(response.status).toBe(200); @@ -865,7 +865,7 @@ describe('Collection Bundles Streaming API', function () { .post('/api/collection-bundles?stream=true&previewOnly=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); expect(response.status).toBe(200); expect(response.headers['content-type']).toBe('text/event-stream'); @@ -883,7 +883,7 @@ describe('Collection Bundles Streaming API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -909,7 +909,7 @@ describe('Collection Bundles Streaming API', function () { .post('/api/collection-bundles?stream=true&forceImport=duplicate-collection') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); expect(response.status).toBe(200); expect(response.headers['content-type']).toBe('text/event-stream'); diff --git a/app/tests/api/collection-indexes/collection-indexes.spec.js b/app/tests/api/collection-indexes/collection-indexes.spec.js index 28cbb2bd..6fcdb547 100644 --- a/app/tests/api/collection-indexes/collection-indexes.spec.js +++ b/app/tests/api/collection-indexes/collection-indexes.spec.js @@ -84,7 +84,7 @@ describe('Collection Indexes Basic API', function () { request(app) .get('/api/collection-indexes') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { @@ -107,7 +107,7 @@ describe('Collection Indexes Basic API', function () { .post('/api/collection-indexes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -121,7 +121,7 @@ describe('Collection Indexes Basic API', function () { .post('/api/collection-indexes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -138,7 +138,7 @@ describe('Collection Indexes Basic API', function () { const res = await request(app) .get('/api/collection-indexes') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -153,7 +153,7 @@ describe('Collection Indexes Basic API', function () { await request(app) .get('/api/collection-indexes/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -161,7 +161,7 @@ describe('Collection Indexes Basic API', function () { const res = await request(app) .get('/api/collection-indexes/' + collectionIndex1.collection_index.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -184,7 +184,7 @@ describe('Collection Indexes Basic API', function () { .put('/api/collection-indexes/' + collectionIndex1.collection_index.id) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -203,14 +203,14 @@ describe('Collection Indexes Basic API', function () { .post('/api/collection-indexes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); it('DELETE /api/collection-indexes deletes a collection index', async function () { await request(app) .delete('/api/collection-indexes/' + collectionIndex1.collection_index.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -218,7 +218,7 @@ describe('Collection Indexes Basic API', function () { const res = await request(app) .get('/api/collection-indexes') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/collections/collections.spec.js b/app/tests/api/collections/collections.spec.js index 0e9e4e60..88e7ecb0 100644 --- a/app/tests/api/collections/collections.spec.js +++ b/app/tests/api/collections/collections.spec.js @@ -150,7 +150,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -178,7 +178,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -195,7 +195,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -217,7 +217,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -234,7 +234,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/collections') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -248,7 +248,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/collections') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -266,7 +266,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -281,7 +281,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { await request(app) .get('/api/collections/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -289,7 +289,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections/' + collection1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -322,7 +322,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections/' + collection1.stix.id + '?retrieveContents=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -356,7 +356,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/collections') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -382,7 +382,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { .post('/api/collections') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -395,7 +395,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections/' + collection2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -413,7 +413,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections/' + collection1.stix.id + '/modified/' + collection1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -435,7 +435,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { '?retrieveContents=true', ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -461,7 +461,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { '?retrieveContents=true', ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -481,7 +481,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -496,7 +496,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections/' + collection1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -510,7 +510,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { it('DELETE /api/collections/:id should not delete a collection when the id cannot be found', async function () { await request(app) .delete('/api/collections/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -523,7 +523,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { collection2.stix.modified + '?deleteAllContents=true', ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -531,7 +531,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { await request(app) .get(`/api/mitigations/${mitigation2.stix.id}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -541,7 +541,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { const res = await request(app) .get('/api/collections/' + collection1.stix.id + '?retrieveContents=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -564,7 +564,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { it('DELETE /api/collections/:id should delete all of the collections with the stix id', async function () { await request(app) .delete('/api/collections/' + collection1.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index be3643c9..3a79fbef 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -66,7 +66,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -83,7 +83,7 @@ describe('Data Components API', function () { .post('/api/data-components') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -97,7 +97,7 @@ describe('Data Components API', function () { .post('/api/data-components') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -119,7 +119,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -134,7 +134,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components/' + dataComponent1.stix.id + '/channels') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -151,7 +151,7 @@ describe('Data Components API', function () { await request(app) .get('/api/data-components/not-a-real-dc-id/channels') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -159,7 +159,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components/' + dataComponent1.stix.id + '/log-sources') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -178,7 +178,7 @@ describe('Data Components API', function () { await request(app) .get('/api/data-components/not-a-real-dc-id/log-sources') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -186,7 +186,7 @@ describe('Data Components API', function () { await request(app) .get('/api/data-components/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -194,7 +194,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components/' + dataComponent1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -238,7 +238,7 @@ describe('Data Components API', function () { .put('/api/data-components/' + dataComponent1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -255,7 +255,7 @@ describe('Data Components API', function () { .post('/api/data-components') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -272,7 +272,7 @@ describe('Data Components API', function () { .post('/api/data-components') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -285,7 +285,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components/' + dataComponent2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -303,7 +303,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components/' + dataComponent1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -323,7 +323,7 @@ describe('Data Components API', function () { dataComponent1.stix.modified, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -344,7 +344,7 @@ describe('Data Components API', function () { dataComponent2.stix.modified, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -369,7 +369,7 @@ describe('Data Components API', function () { .post('/api/data-components') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -381,7 +381,7 @@ describe('Data Components API', function () { it('DELETE /api/data-components/:id should not delete a data component when the id cannot be found', async function () { await request(app) .delete('/api/data-components/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -393,14 +393,14 @@ describe('Data Components API', function () { '/modified/' + dataComponent1.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/data-components/:id should delete all the data components with the same stix id', async function () { await request(app) .delete('/api/data-components/' + dataComponent2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -408,7 +408,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index e2a07c00..77049c9f 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -114,7 +114,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); // We expect to get an empty array @@ -130,7 +130,7 @@ describe('Data Sources API', function () { .post('/api/data-sources') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -144,7 +144,7 @@ describe('Data Sources API', function () { .post('/api/data-sources') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -162,7 +162,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -177,7 +177,7 @@ describe('Data Sources API', function () { await request(app) .get('/api/data-sources/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -185,7 +185,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources/' + dataSource1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -220,7 +220,7 @@ describe('Data Sources API', function () { const res = await request(app) .get(`/api/data-sources/${dataSource1.stix.id}?retrieveDataComponents=true`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -247,7 +247,7 @@ describe('Data Sources API', function () { .put('/api/data-sources/' + dataSource1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -264,7 +264,7 @@ describe('Data Sources API', function () { .post('/api/data-sources') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -281,7 +281,7 @@ describe('Data Sources API', function () { .post('/api/data-sources') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -294,7 +294,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources/' + dataSource2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -312,7 +312,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources/' + dataSource1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -327,7 +327,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources/' + dataSource1.stix.id + '/modified/' + dataSource1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -343,7 +343,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources/' + dataSource2.stix.id + '/modified/' + dataSource2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -368,7 +368,7 @@ describe('Data Sources API', function () { .post('/api/data-sources') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -380,21 +380,21 @@ describe('Data Sources API', function () { it('DELETE /api/data-sources/:id should not delete a data source when the id cannot be found', async function () { await request(app) .delete('/api/data-sources/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/data-sources/:id/modified/:modified deletes a data source', async function () { await request(app) .delete('/api/data-sources/' + dataSource1.stix.id + '/modified/' + dataSource1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/data-sources/:id should delete all the data sources with the same stix id', async function () { await request(app) .delete('/api/data-sources/' + dataSource2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -402,7 +402,7 @@ describe('Data Sources API', function () { const res = await request(app) .get('/api/data-sources') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index a873e902..752ccf37 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -65,7 +65,7 @@ describe('Detection Strategies API', function () { const res = await request(app) .get('/api/detection-strategies') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -82,7 +82,7 @@ describe('Detection Strategies API', function () { .post('/api/detection-strategies') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -96,7 +96,7 @@ describe('Detection Strategies API', function () { .post('/api/detection-strategies') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -117,7 +117,7 @@ describe('Detection Strategies API', function () { const res = await request(app) .get('/api/detection-strategies') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -132,7 +132,7 @@ describe('Detection Strategies API', function () { await request(app) .get('/api/detection-strategies/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -140,7 +140,7 @@ describe('Detection Strategies API', function () { const res = await request(app) .get('/api/detection-strategies/' + detectionStrategy1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -186,7 +186,7 @@ describe('Detection Strategies API', function () { ) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -203,7 +203,7 @@ describe('Detection Strategies API', function () { .post('/api/detection-strategies') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -220,7 +220,7 @@ describe('Detection Strategies API', function () { .post('/api/detection-strategies') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -242,7 +242,7 @@ describe('Detection Strategies API', function () { .post('/api/detection-strategies') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -255,7 +255,7 @@ describe('Detection Strategies API', function () { const res = await request(app) .get('/api/detection-strategies/' + detectionStrategy3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -273,7 +273,7 @@ describe('Detection Strategies API', function () { const res = await request(app) .get('/api/detection-strategies/' + detectionStrategy1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -293,7 +293,7 @@ describe('Detection Strategies API', function () { detectionStrategy1.stix.modified, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -314,7 +314,7 @@ describe('Detection Strategies API', function () { detectionStrategy2.stix.modified, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -329,7 +329,7 @@ describe('Detection Strategies API', function () { it('DELETE /api/detection-strategies/:id should not delete a detection strategy when the id cannot be found', async function () { await request(app) .delete('/api/detection-strategies/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -341,14 +341,14 @@ describe('Detection Strategies API', function () { '/modified/' + detectionStrategy1.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/detection-strategies/:id should delete all the detection strategies with the same stix id', async function () { await request(app) .delete('/api/detection-strategies/' + detectionStrategy2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -356,7 +356,7 @@ describe('Detection Strategies API', function () { const res = await request(app) .get('/api/detection-strategies') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/groups/groups-input-validation.spec.js b/app/tests/api/groups/groups-input-validation.spec.js index ba2c73ca..bec54ee7 100644 --- a/app/tests/api/groups/groups-input-validation.spec.js +++ b/app/tests/api/groups/groups-input-validation.spec.js @@ -106,7 +106,7 @@ describe('Groups API Input Validation', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -120,7 +120,7 @@ describe('Groups API Input Validation', function () { .post('/api/groups?not-a-parameter=unexpectedvalue') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -135,7 +135,7 @@ describe('Groups API Input Validation', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -150,7 +150,7 @@ describe('Groups API Input Validation', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 7819a2e6..2d8de647 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -183,7 +183,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -198,7 +198,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups?includeDeprecated=false') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -213,7 +213,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups?includeDeprecated=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -228,7 +228,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups?includeRevoked=false') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -243,7 +243,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups?includeRevoked=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -258,7 +258,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups?state=work-in-progress') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -276,7 +276,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get('/api/groups?search=G0001') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -297,7 +297,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get(`/api/groups?lastUpdatedBy=${userAccount1.id}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -315,7 +315,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get(`/api/groups?lastUpdatedBy=${userAccount2.id}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -332,7 +332,7 @@ describe('Groups API Queries', function () { const res = await request(app) .get(`/api/groups?lastUpdatedBy=${userAccount1.id}&lastUpdatedBy=${userAccount2.id}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/groups/groups.spec.js b/app/tests/api/groups/groups.spec.js index 515084b7..49cdc58e 100644 --- a/app/tests/api/groups/groups.spec.js +++ b/app/tests/api/groups/groups.spec.js @@ -93,7 +93,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -110,7 +110,7 @@ describe('Groups API', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -124,7 +124,7 @@ describe('Groups API', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); // We expect to get the created group @@ -147,7 +147,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -162,7 +162,7 @@ describe('Groups API', function () { await request(app) .get('/api/groups/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -170,7 +170,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups/' + group1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -205,7 +205,7 @@ describe('Groups API', function () { .put('/api/groups/' + group1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -222,7 +222,7 @@ describe('Groups API', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -246,7 +246,7 @@ describe('Groups API', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -259,7 +259,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups/' + group2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -284,7 +284,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups/' + group1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -299,7 +299,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups/' + group1.stix.id + '/modified/' + group1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -315,7 +315,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups/' + group2.stix.id + '/modified/' + group2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -344,7 +344,7 @@ describe('Groups API', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -368,7 +368,7 @@ describe('Groups API', function () { .post('/api/groups') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -381,7 +381,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups?search=yellow') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -403,7 +403,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups?search=blue') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -418,7 +418,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups?search=brown') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -439,28 +439,28 @@ describe('Groups API', function () { it('DELETE /api/groups/:id should not delete a group when the id cannot be found', async function () { await request(app) .delete('/api/groups/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/groups/:id/modified/:modified deletes a group', async function () { await request(app) .delete('/api/groups/' + group1.stix.id + '/modified/' + group1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/groups/:id should delete all the groups with the same stix id', async function () { await request(app) .delete('/api/groups/' + group2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/groups/:id/modified/:modified should delete the third group', async function () { await request(app) .delete('/api/groups/' + group3.stix.id + '/modified/' + group3.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -468,7 +468,7 @@ describe('Groups API', function () { const res = await request(app) .get('/api/groups') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/identities/identities.spec.js b/app/tests/api/identities/identities.spec.js index ad9c9f3e..4a4f2e46 100644 --- a/app/tests/api/identities/identities.spec.js +++ b/app/tests/api/identities/identities.spec.js @@ -52,7 +52,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -69,7 +69,7 @@ describe('Identity API', function () { .post('/api/identities') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -83,7 +83,7 @@ describe('Identity API', function () { .post('/api/identities') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -101,7 +101,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -116,7 +116,7 @@ describe('Identity API', function () { await request(app) .get('/api/identities/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -124,7 +124,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities/' + identity1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -161,7 +161,7 @@ describe('Identity API', function () { .put('/api/identities/' + identity1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -178,7 +178,7 @@ describe('Identity API', function () { .post('/api/identities') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -195,7 +195,7 @@ describe('Identity API', function () { .post('/api/identities') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -208,7 +208,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities/' + identity2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -226,7 +226,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities/' + identity1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -241,7 +241,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities/' + identity1.stix.id + '/modified/' + identity1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -257,7 +257,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities/' + identity2.stix.id + '/modified/' + identity2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -282,7 +282,7 @@ describe('Identity API', function () { .post('/api/identities') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -294,21 +294,21 @@ describe('Identity API', function () { it('DELETE /api/identities/:id should not delete a identity when the id cannot be found', async function () { await request(app) .delete('/api/identities/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/identities/:id/modified/:modified deletes an identity', async function () { await request(app) .delete('/api/identities/' + identity1.stix.id + '/modified/' + identity1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/identities/:id should delete all the identities with the same stix id', async function () { await request(app) .delete('/api/identities/' + identity2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -316,7 +316,7 @@ describe('Identity API', function () { const res = await request(app) .get('/api/identities') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/marking-definitions/marking-definitions.spec.js b/app/tests/api/marking-definitions/marking-definitions.spec.js index d0b5707a..58e94aa5 100644 --- a/app/tests/api/marking-definitions/marking-definitions.spec.js +++ b/app/tests/api/marking-definitions/marking-definitions.spec.js @@ -49,7 +49,7 @@ describe('Marking Definitions API', function () { const res = await request(app) .get('/api/marking-definitions') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -66,7 +66,7 @@ describe('Marking Definitions API', function () { .post('/api/marking-definitions') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -79,7 +79,7 @@ describe('Marking Definitions API', function () { .post('/api/marking-definitions') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -95,7 +95,7 @@ describe('Marking Definitions API', function () { const res = await request(app) .get('/api/marking-definitions') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -114,7 +114,7 @@ describe('Marking Definitions API', function () { await request(app) .get('/api/marking-definitions/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -122,7 +122,7 @@ describe('Marking Definitions API', function () { const res = await request(app) .get('/api/marking-definitions/' + markingDefinition1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -153,7 +153,7 @@ describe('Marking Definitions API', function () { .put('/api/marking-definitions/' + markingDefinition1.stix.id) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -169,14 +169,14 @@ describe('Marking Definitions API', function () { .post('/api/marking-definitions') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); it('DELETE /api/marking-definitions deletes a marking definition', async function () { await request(app) .delete('/api/marking-definitions/' + markingDefinition1.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -184,7 +184,7 @@ describe('Marking Definitions API', function () { const res = await request(app) .get('/api/marking-definitions') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/matrices/matrices.spec.js b/app/tests/api/matrices/matrices.spec.js index 01ea2922..57f09305 100644 --- a/app/tests/api/matrices/matrices.spec.js +++ b/app/tests/api/matrices/matrices.spec.js @@ -68,7 +68,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -85,7 +85,7 @@ describe('Matrices API', function () { .post('/api/matrices') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -99,7 +99,7 @@ describe('Matrices API', function () { .post('/api/matrices') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -117,7 +117,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -132,7 +132,7 @@ describe('Matrices API', function () { await request(app) .get('/api/matrices/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -140,7 +140,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices/' + matrix1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -178,7 +178,7 @@ describe('Matrices API', function () { .put('/api/matrices/' + matrix1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -195,7 +195,7 @@ describe('Matrices API', function () { .post('/api/matrices') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -213,7 +213,7 @@ describe('Matrices API', function () { .post('/api/matrices') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -236,7 +236,7 @@ describe('Matrices API', function () { .post('/api/matrices') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -249,7 +249,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices/' + matrix3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); // We expect to get one matrix in an array @@ -266,7 +266,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices/' + matrix1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -281,7 +281,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices/' + matrix1.stix.id + '/modified/' + matrix1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); // We expect to get one matrix in an array @@ -296,7 +296,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices/' + matrix2.stix.id + '/modified/' + matrix2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -311,21 +311,21 @@ describe('Matrices API', function () { it('DELETE /api/matrices/:id should not delete a matrix when the id cannot be found', async function () { await request(app) .delete('/api/matrices/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/matrices/:id/modified/:modified deletes a matrix', async function () { await request(app) .delete('/api/matrices/' + matrix1.stix.id + '/modified/' + matrix1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/matrices/:id should delete all the matrices with the same stix id', async function () { await request(app) .delete('/api/matrices/' + matrix2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -333,7 +333,7 @@ describe('Matrices API', function () { const res = await request(app) .get('/api/matrices') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -358,7 +358,7 @@ describe('Matrices API', function () { '/api/matrices/x-mitre-matrix--2a4858a3-85c3-4418-9729-c3e79800acf7/modified/2020-01-01T00:00:00.000Z/techniques', ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/mitigations/mitigations.spec.js b/app/tests/api/mitigations/mitigations.spec.js index ca5894c5..f060b509 100644 --- a/app/tests/api/mitigations/mitigations.spec.js +++ b/app/tests/api/mitigations/mitigations.spec.js @@ -55,7 +55,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -72,7 +72,7 @@ describe('Mitigations API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -86,7 +86,7 @@ describe('Mitigations API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -108,7 +108,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -123,7 +123,7 @@ describe('Mitigations API', function () { await request(app) .get('/api/mitigations/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -131,7 +131,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations/' + mitigation1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -173,7 +173,7 @@ describe('Mitigations API', function () { .put('/api/mitigations/' + mitigation1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -190,7 +190,7 @@ describe('Mitigations API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -207,7 +207,7 @@ describe('Mitigations API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -229,7 +229,7 @@ describe('Mitigations API', function () { .post('/api/mitigations') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -242,7 +242,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations/' + mitigation3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -260,7 +260,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations/' + mitigation1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -275,7 +275,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations/' + mitigation1.stix.id + '/modified/' + mitigation1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -291,7 +291,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations/' + mitigation2.stix.id + '/modified/' + mitigation2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -306,21 +306,21 @@ describe('Mitigations API', function () { it('DELETE /api/mitigations/:id should not delete a mitigation when the id cannot be found', async function () { await request(app) .delete('/api/mitigations/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/mitigations/:id/modified/:modified deletes a mitigation', async function () { await request(app) .delete('/api/mitigations/' + mitigation1.stix.id + '/modified/' + mitigation1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/mitigations/:id should delete all the mitigations with the same stix id', async function () { await request(app) .delete('/api/mitigations/' + mitigation2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -328,7 +328,7 @@ describe('Mitigations API', function () { const res = await request(app) .get('/api/mitigations') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/notes/notes.spec.js b/app/tests/api/notes/notes.spec.js index b47ed430..d455b599 100644 --- a/app/tests/api/notes/notes.spec.js +++ b/app/tests/api/notes/notes.spec.js @@ -55,7 +55,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -72,7 +72,7 @@ describe('Notes API', function () { .post('/api/notes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -86,7 +86,7 @@ describe('Notes API', function () { .post('/api/notes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -104,7 +104,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -119,7 +119,7 @@ describe('Notes API', function () { const res = await request(app) .get(`/api/notes?lastUpdatedBy=${note1.workspace.workflow.created_by_user_account}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -134,7 +134,7 @@ describe('Notes API', function () { const res = await request(app) .get(`/api/notes?lastUpdatedBy=identity--11111111-1111-1111-1111-111111111111`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -149,7 +149,7 @@ describe('Notes API', function () { await request(app) .get('/api/notes/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -157,7 +157,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes/' + note1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -190,7 +190,7 @@ describe('Notes API', function () { .put('/api/notes/' + note1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -207,7 +207,7 @@ describe('Notes API', function () { .post('/api/notes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -226,7 +226,7 @@ describe('Notes API', function () { .post('/api/notes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -239,7 +239,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes/' + note2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -269,7 +269,7 @@ describe('Notes API', function () { .post('/api/notes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -293,7 +293,7 @@ describe('Notes API', function () { .post('/api/notes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -306,7 +306,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes/' + note1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -321,7 +321,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes/' + note1.stix.id + '/modified/' + note1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -337,7 +337,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes/' + note2.stix.id + '/modified/' + note2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -353,7 +353,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes/') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -368,7 +368,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes?search=PARCHMENT') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -383,7 +383,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes?search=IVORY') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -397,28 +397,28 @@ describe('Notes API', function () { it('DELETE /api/notes/:id should not delete a note when the id cannot be found', async function () { await request(app) .delete('/api/notes/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/notes/:id/modified/:modified should delete the first version of the note', async function () { await request(app) .delete('/api/notes/' + note1.stix.id + '/modified/' + note1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/notes/:id/modified/:modified should delete the second version of the note', async function () { await request(app) .delete('/api/notes/' + note2.stix.id + '/modified/' + note2.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/notes/:id should delete all versions of the note', async function () { await request(app) .delete('/api/notes/' + note3.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -426,7 +426,7 @@ describe('Notes API', function () { const res = await request(app) .get('/api/notes') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/recent-activity/recent-activity.spec.js b/app/tests/api/recent-activity/recent-activity.spec.js index e7ec1073..483f82da 100644 --- a/app/tests/api/recent-activity/recent-activity.spec.js +++ b/app/tests/api/recent-activity/recent-activity.spec.js @@ -41,7 +41,7 @@ describe('Recent Activity API', function () { const res = await request(app) .get('/api/recent-activity') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -68,7 +68,7 @@ describe('Recent Activity API', function () { const res = await request(app) .get('/api/recent-activity') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/references/references.spec.js b/app/tests/api/references/references.spec.js index 32aedffe..3ee97184 100644 --- a/app/tests/api/references/references.spec.js +++ b/app/tests/api/references/references.spec.js @@ -56,7 +56,7 @@ describe('References API', function () { const res = await request(app) .get('/api/references') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -73,7 +73,7 @@ describe('References API', function () { .post('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -84,7 +84,7 @@ describe('References API', function () { .post('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -100,7 +100,7 @@ describe('References API', function () { .post('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -116,7 +116,7 @@ describe('References API', function () { .post('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -129,7 +129,7 @@ describe('References API', function () { const res = await request(app) .get('/api/references') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -144,7 +144,7 @@ describe('References API', function () { const res = await request(app) .get('/api/references?sourceName=notasourcename') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); // We expect to get an empty array @@ -158,7 +158,7 @@ describe('References API', function () { const res = await request(app) .get('/api/references?sourceName=' + encodeURIComponent(reference1.source_name)) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -179,7 +179,7 @@ describe('References API', function () { const res = await request(app) .get('/api/references?search=' + encodeURIComponent('#3')) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -200,7 +200,7 @@ describe('References API', function () { const res = await request(app) .get('/api/references?search=' + encodeURIComponent('unique')) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -223,7 +223,7 @@ describe('References API', function () { .put('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -237,7 +237,7 @@ describe('References API', function () { .put('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -248,7 +248,7 @@ describe('References API', function () { .put('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -265,7 +265,7 @@ describe('References API', function () { .post('/api/references') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -273,7 +273,7 @@ describe('References API', function () { await request(app) .delete('/api/references') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -281,7 +281,7 @@ describe('References API', function () { await request(app) .delete('/api/references?sourceName=not-a-reference') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -289,7 +289,7 @@ describe('References API', function () { await request(app) .delete(`/api/references?sourceName=${reference1.source_name}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 06f6536e..e5e66987 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -64,7 +64,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); // We expect to get an empty array @@ -80,7 +80,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -94,7 +94,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -112,7 +112,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -127,7 +127,7 @@ describe('Relationships API', function () { await request(app) .get('/api/relationships/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -135,7 +135,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships/' + relationship1a.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -172,7 +172,7 @@ describe('Relationships API', function () { .put('/api/relationships/' + relationship1a.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -189,7 +189,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -206,7 +206,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -228,7 +228,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -241,7 +241,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -259,7 +259,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -274,7 +274,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships/' + relationship1b.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -292,7 +292,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships/' + relationship1a.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -312,7 +312,7 @@ describe('Relationships API', function () { relationship1a.stix.modified, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -333,7 +333,7 @@ describe('Relationships API', function () { relationship1b.stix.modified, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -357,7 +357,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -370,7 +370,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?sourceRef=' + sourceRef1) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -385,7 +385,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?targetRef=' + targetRef1) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -400,7 +400,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?sourceOrTargetRef=' + sourceRef1) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -415,7 +415,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?sourceRef=' + sourceRef3) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -430,7 +430,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?targetRef=' + targetRef3) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -445,7 +445,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships?sourceOrTargetRef=' + sourceRef3) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -459,7 +459,7 @@ describe('Relationships API', function () { it('DELETE /api/relationships/:id should not delete a relationship when the id cannot be found', async function () { await request(app) .delete('/api/relationships/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -471,14 +471,14 @@ describe('Relationships API', function () { '/modified/' + relationship1a.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/relationships/:id should delete all the relationships with the same stix id', async function () { await request(app) .delete('/api/relationships/' + relationship1b.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -487,7 +487,7 @@ describe('Relationships API', function () { .delete( '/api/relationships/' + relationship2.stix.id + '/modified/' + relationship2.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -495,7 +495,7 @@ describe('Relationships API', function () { const res = await request(app) .get('/api/relationships') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/software/software.spec.js b/app/tests/api/software/software.spec.js index 5052b621..b3c79800 100644 --- a/app/tests/api/software/software.spec.js +++ b/app/tests/api/software/software.spec.js @@ -71,7 +71,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -88,7 +88,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -101,7 +101,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -114,7 +114,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -128,7 +128,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -146,7 +146,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -161,7 +161,7 @@ describe('Software API', function () { await request(app) .get('/api/software/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -169,7 +169,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software/' + software1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -217,7 +217,7 @@ describe('Software API', function () { .put('/api/software/' + software1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -234,7 +234,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -251,7 +251,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -264,7 +264,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software/' + software2.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -282,7 +282,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software/' + software1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -297,7 +297,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software/' + software1.stix.id + '/modified/' + software1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -313,7 +313,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software/' + software2.stix.id + '/modified/' + software2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -338,7 +338,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -350,21 +350,21 @@ describe('Software API', function () { it('DELETE /api/software/:id should not delete a software when the id cannot be found', async function () { await request(app) .delete('/api/software/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/software/:id/modified/:modified deletes a software', async function () { await request(app) .delete('/api/software/' + software1.stix.id + '/modified/' + software1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/software/:id should delete all the software with the same stix id', async function () { await request(app) .delete('/api/software/' + software2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -372,7 +372,7 @@ describe('Software API', function () { const res = await request(app) .get('/api/software') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -392,7 +392,7 @@ describe('Software API', function () { .post('/api/software') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); diff --git a/app/tests/api/stix-bundles/stix-bundles-old.spec.js b/app/tests/api/stix-bundles/stix-bundles-old.spec.js index 58cc042c..649faf98 100644 --- a/app/tests/api/stix-bundles/stix-bundles-old.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles-old.spec.js @@ -683,7 +683,7 @@ describe('STIX Bundles Basic API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/) .end(function (err, res) { @@ -707,7 +707,7 @@ describe('STIX Bundles Basic API', function () { .get('/api/stix-bundles?domain=not-a-domain') .query({ useLegacyMethod: true }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .end(function (err, res) { if (err) { @@ -728,7 +728,7 @@ describe('STIX Bundles Basic API', function () { .get(`/api/stix-bundles?domain=${enterpriseDomain}&includeNotes=true`) .query({ useLegacyMethod: true }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { @@ -762,7 +762,7 @@ describe('STIX Bundles Basic API', function () { ) .query({ useLegacyMethod: true }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { @@ -802,7 +802,7 @@ describe('STIX Bundles Basic API', function () { .get(`/api/stix-bundles?domain=${enterpriseDomain}&includeDeprecated=true&includeNotes=true`) .query({ useLegacyMethod: true }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { @@ -827,7 +827,7 @@ describe('STIX Bundles Basic API', function () { .get(`/api/stix-bundles?domain=${mobileDomain}`) .query({ useLegacyMethod: true }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { @@ -851,7 +851,7 @@ describe('STIX Bundles Basic API', function () { .get(`/api/stix-bundles?domain=${icsDomain}`) .query({ useLegacyMethod: true }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .end(function (err, res) { diff --git a/app/tests/api/stix-bundles/stix-bundles.spec.js b/app/tests/api/stix-bundles/stix-bundles.spec.js index 153a2814..ef6243d2 100644 --- a/app/tests/api/stix-bundles/stix-bundles.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles.spec.js @@ -477,7 +477,7 @@ describe('STIX Bundles New Specification API', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -493,7 +493,7 @@ describe('STIX Bundles New Specification API', function () { request(app) .get('/api/stix-bundles?domain=not-a-domain') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .end(function (err, res) { if (err) { @@ -515,7 +515,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: enterpriseDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -555,7 +555,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: enterpriseDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -585,7 +585,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: enterpriseDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -613,7 +613,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: enterpriseDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -637,7 +637,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: enterpriseDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -655,7 +655,7 @@ describe('STIX Bundles New Specification API', function () { .query({ includeDataSources: true }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -672,7 +672,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: enterpriseDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -688,7 +688,7 @@ describe('STIX Bundles New Specification API', function () { .query({ domain: icsDomain }) .query({ stixVersion: '2.1' }) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/system-configuration/create-object-identity.spec.js b/app/tests/api/system-configuration/create-object-identity.spec.js index 0bdbc2b3..0f54ed63 100644 --- a/app/tests/api/system-configuration/create-object-identity.spec.js +++ b/app/tests/api/system-configuration/create-object-identity.spec.js @@ -66,7 +66,7 @@ describe('Create Object with Organization Identity API', function () { const res = await request(app) .get('/api/config/organization-identity') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -86,7 +86,7 @@ describe('Create Object with Organization Identity API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -113,7 +113,7 @@ describe('Create Object with Organization Identity API', function () { .post('/api/identities') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -135,7 +135,7 @@ describe('Create Object with Organization Identity API', function () { .post('/api/config/organization-identity') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -151,7 +151,7 @@ describe('Create Object with Organization Identity API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); diff --git a/app/tests/api/system-configuration/system-configuration.spec.js b/app/tests/api/system-configuration/system-configuration.spec.js index 17279e35..92903ea1 100644 --- a/app/tests/api/system-configuration/system-configuration.spec.js +++ b/app/tests/api/system-configuration/system-configuration.spec.js @@ -48,7 +48,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/system-version') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -63,7 +63,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/allowed-values') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -99,7 +99,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/organization-identity') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -112,7 +112,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/authn') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -128,7 +128,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/marking-definitions') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -149,7 +149,7 @@ describe('System Configuration API', function () { .put('/api/marking-definitions/' + amberTlpMarkingDefinition.stix.id) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -157,7 +157,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/default-marking-definitions') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -177,7 +177,7 @@ describe('System Configuration API', function () { .post('/api/marking-definitions') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -192,7 +192,7 @@ describe('System Configuration API', function () { .post('/api/config/default-marking-definitions') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); // We expect the response body to be an empty object @@ -204,7 +204,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/default-marking-definitions') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -220,7 +220,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/default-marking-definitions?refOnly=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -236,7 +236,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/organization-namespace') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -254,7 +254,7 @@ describe('System Configuration API', function () { .post('/api/config/organization-namespace') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); // We expect the response body to be an empty object @@ -266,7 +266,7 @@ describe('System Configuration API', function () { const res = await request(app) .get('/api/config/organization-namespace') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index 7097e94a..25be327f 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -52,7 +52,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -69,7 +69,7 @@ describe('Tactics API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -83,7 +83,7 @@ describe('Tactics API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -104,7 +104,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -119,7 +119,7 @@ describe('Tactics API', function () { await request(app) .get('/api/tactics/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -127,7 +127,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics/' + tactic1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -165,7 +165,7 @@ describe('Tactics API', function () { .put('/api/tactics/' + tactic1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -183,7 +183,7 @@ describe('Tactics API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -201,7 +201,7 @@ describe('Tactics API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -224,7 +224,7 @@ describe('Tactics API', function () { .post('/api/tactics') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -237,7 +237,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics/' + tactic3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -255,7 +255,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics/' + tactic1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -270,7 +270,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics/' + tactic1.stix.id + '/modified/' + tactic1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -286,7 +286,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics/' + tactic2.stix.id + '/modified/' + tactic2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -302,7 +302,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics?search=violet') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -324,7 +324,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics?search=yellow') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -338,21 +338,21 @@ describe('Tactics API', function () { it('DELETE /api/tactics/:id should not delete a tactic when the id cannot be found', async function () { await request(app) .delete('/api/tactics/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/tactics/:id/modified/:modified deletes a tactic', async function () { await request(app) .delete('/api/tactics/' + tactic1.stix.id + '/modified/' + tactic1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/tactics/:id should delete all the tactics with the same stix id', async function () { await request(app) .delete('/api/tactics/' + tactic2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -360,7 +360,7 @@ describe('Tactics API', function () { const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index 2fc21bfb..105f9ea1 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -51,7 +51,7 @@ describe('Tactics with Techniques API', function () { const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -68,7 +68,7 @@ describe('Tactics with Techniques API', function () { const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -82,7 +82,7 @@ describe('Tactics with Techniques API', function () { await request(app) .get(`/api/tactics/not-an-id/modified/2022-01-01T00:00:00.000Z/techniques`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -90,7 +90,7 @@ describe('Tactics with Techniques API', function () { const res = await request(app) .get(`/api/tactics/${tactic1.stix.id}/modified/${tactic1.stix.modified}/techniques`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -106,7 +106,7 @@ describe('Tactics with Techniques API', function () { `/api/tactics/${tactic2.stix.id}/modified/${tactic2.stix.modified}/techniques?offset=0&limit=2&includePagination=true`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/teams/teams-invalid.spec.js b/app/tests/api/teams/teams-invalid.spec.js index ae1c6e13..75d7f490 100644 --- a/app/tests/api/teams/teams-invalid.spec.js +++ b/app/tests/api/teams/teams-invalid.spec.js @@ -36,7 +36,7 @@ describe('Teams API Test Invalid Data', function () { .post('/api/teams') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); } diff --git a/app/tests/api/teams/teams.spec.js b/app/tests/api/teams/teams.spec.js index 3a60c8f0..fdf761e1 100644 --- a/app/tests/api/teams/teams.spec.js +++ b/app/tests/api/teams/teams.spec.js @@ -63,7 +63,7 @@ describe('Teams API', function () { .post('/api/teams') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -74,7 +74,7 @@ describe('Teams API', function () { .post('/api/teams') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -88,7 +88,7 @@ describe('Teams API', function () { const res = await request(app) .get('/api/teams') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -103,7 +103,7 @@ describe('Teams API', function () { await request(app) .get('/api/teams/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -111,7 +111,7 @@ describe('Teams API', function () { const res = await request(app) .get('/api/teams/' + team1.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -137,7 +137,7 @@ describe('Teams API', function () { .put('/api/teams/' + team1.id) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -156,7 +156,7 @@ describe('Teams API', function () { const res = await request(app) .get('/api/teams?search=team') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -173,7 +173,7 @@ describe('Teams API', function () { .post('/api/teams') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -183,7 +183,7 @@ describe('Teams API', function () { .get(`/api/teams/${team1.id}/users`) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -198,7 +198,7 @@ describe('Teams API', function () { it('DELETE /api/teams deletes a teams', async function () { await request(app) .delete('/api/teams/' + team1.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); diff --git a/app/tests/api/techniques/techniques.query.spec.js b/app/tests/api/techniques/techniques.query.spec.js index 340af8be..1c5a9b95 100644 --- a/app/tests/api/techniques/techniques.query.spec.js +++ b/app/tests/api/techniques/techniques.query.spec.js @@ -153,7 +153,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -168,7 +168,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?includeDeprecated=false') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -183,7 +183,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?includeDeprecated=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -198,7 +198,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?includeRevoked=false') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -213,7 +213,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?includeRevoked=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -228,7 +228,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?state=work-in-progress') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -243,7 +243,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?state=work-in-progress&state=reviewed') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -258,7 +258,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?domain=mobile-attack') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -273,7 +273,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?domain=not-a-domain') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -287,7 +287,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?platform=platform-3') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -302,7 +302,7 @@ describe('Techniques Query API', function () { const res = await request(app) .get('/api/techniques?platform=not-a-platform') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/techniques/techniques.spec.js b/app/tests/api/techniques/techniques.spec.js index 743a19b7..cb124d96 100644 --- a/app/tests/api/techniques/techniques.spec.js +++ b/app/tests/api/techniques/techniques.spec.js @@ -68,7 +68,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -85,7 +85,7 @@ describe('Techniques Basic API', function () { .post('/api/techniques') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -99,7 +99,7 @@ describe('Techniques Basic API', function () { .post('/api/techniques') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -119,7 +119,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -134,7 +134,7 @@ describe('Techniques Basic API', function () { await request(app) .get('/api/techniques/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -142,7 +142,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques/' + technique1.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -206,7 +206,7 @@ describe('Techniques Basic API', function () { .put('/api/techniques/' + technique1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -223,7 +223,7 @@ describe('Techniques Basic API', function () { .post('/api/techniques') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); @@ -241,7 +241,7 @@ describe('Techniques Basic API', function () { .post('/api/techniques') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -264,7 +264,7 @@ describe('Techniques Basic API', function () { .post('/api/techniques') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -277,7 +277,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques/' + technique3.stix.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -295,7 +295,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques/' + technique1.stix.id + '?versions=all') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -310,7 +310,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques/' + technique1.stix.id + '/modified/' + technique1.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -326,7 +326,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques/' + technique2.stix.id + '/modified/' + technique2.stix.modified) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -342,7 +342,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques?search=blue') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -364,7 +364,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques?search=T9999') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -387,7 +387,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques?search=orange') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -401,21 +401,21 @@ describe('Techniques Basic API', function () { it('DELETE /api/techniques/:id should not delete a technique when the id cannot be found', async function () { await request(app) .delete('/api/techniques/not-an-id') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); it('DELETE /api/techniques/:id/modified/:modified deletes a technique', async function () { await request(app) .delete('/api/techniques/' + technique1.stix.id + '/modified/' + technique1.stix.modified) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); it('DELETE /api/techniques/:id should delete all the techniques with the same stix id', async function () { await request(app) .delete('/api/techniques/' + technique2.stix.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -423,7 +423,7 @@ describe('Techniques Basic API', function () { const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/techniques/techniques.tactics.spec.js b/app/tests/api/techniques/techniques.tactics.spec.js index 055a0bef..b5b690ef 100644 --- a/app/tests/api/techniques/techniques.tactics.spec.js +++ b/app/tests/api/techniques/techniques.tactics.spec.js @@ -54,7 +54,7 @@ describe('Techniques with Tactics API', function () { const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -71,7 +71,7 @@ describe('Techniques with Tactics API', function () { const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -85,7 +85,7 @@ describe('Techniques with Tactics API', function () { await request(app) .get(`/api/techniques/not-an-id/modified/2022-01-01T00:00:00.000Z/tactics`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -93,7 +93,7 @@ describe('Techniques with Tactics API', function () { const res = await request(app) .get(`/api/techniques/${technique1.stix.id}/modified/${technique1.stix.modified}/tactics`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -107,7 +107,7 @@ describe('Techniques with Tactics API', function () { const res = await request(app) .get(`/api/techniques/${technique2.stix.id}/modified/${technique2.stix.modified}/tactics`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -123,7 +123,7 @@ describe('Techniques with Tactics API', function () { `/api/techniques/${technique2.stix.id}/modified/${technique2.stix.modified}/tactics?offset=0&limit=2&includePagination=true`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/api/user-accounts/user-accounts-invalid.spec.js b/app/tests/api/user-accounts/user-accounts-invalid.spec.js index be21b482..46aa3016 100644 --- a/app/tests/api/user-accounts/user-accounts-invalid.spec.js +++ b/app/tests/api/user-accounts/user-accounts-invalid.spec.js @@ -36,7 +36,7 @@ describe('User Accounts API Test Invalid Data', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); } diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index f5b4257f..11981eea 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -48,7 +48,7 @@ describe('User Accounts API', function () { const res = await request(app) .get('/api/user-accounts') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -65,7 +65,7 @@ describe('User Accounts API', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); @@ -76,7 +76,7 @@ describe('User Accounts API', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -90,7 +90,7 @@ describe('User Accounts API', function () { const res = await request(app) .get('/api/user-accounts') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -105,7 +105,7 @@ describe('User Accounts API', function () { await request(app) .get('/api/user-accounts/not-an-id') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); @@ -113,7 +113,7 @@ describe('User Accounts API', function () { const res = await request(app) .get('/api/user-accounts/' + userAccount1.id) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -137,7 +137,7 @@ describe('User Accounts API', function () { const res = await request(app) .get('/api/user-accounts/' + userAccount1.id + '?includeStixIdentity=true') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -173,7 +173,7 @@ describe('User Accounts API', function () { .put('/api/user-accounts/' + userAccount1.id) .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -193,7 +193,7 @@ describe('User Accounts API', function () { const res = await request(app) .get('/api/user-accounts?search=first') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -210,14 +210,14 @@ describe('User Accounts API', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); it('DELETE /api/user-accounts deletes a user account', async function () { await request(app) .delete('/api/user-accounts/' + userAccount1.id) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -226,7 +226,7 @@ describe('User Accounts API', function () { const res = await request(app) .get('/api/user-accounts') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -242,7 +242,7 @@ describe('User Accounts API', function () { const res = await request(app) .get(`/api/user-accounts/${anonymousUserId}/teams`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -266,7 +266,7 @@ describe('User Accounts API', function () { const res = await request(app) .get(`/api/user-accounts/${anonymousUserId}/teams`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/fuzz/user-accounts-fuzz.spec.js b/app/tests/fuzz/user-accounts-fuzz.spec.js index 3f9388a4..d0997fd6 100644 --- a/app/tests/fuzz/user-accounts-fuzz.spec.js +++ b/app/tests/fuzz/user-accounts-fuzz.spec.js @@ -63,7 +63,7 @@ describe('User Accounts API Test Invalid Data', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); } @@ -84,7 +84,7 @@ describe('User Accounts API Test Invalid Data', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); }); } @@ -107,7 +107,7 @@ describe('User Accounts API Test Invalid Data', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); } @@ -131,7 +131,7 @@ describe('User Accounts API Test Invalid Data', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); }); } diff --git a/app/tests/import/collection-bundles-enterprise.spec.js b/app/tests/import/collection-bundles-enterprise.spec.js index a8afa6ad..3402e372 100644 --- a/app/tests/import/collection-bundles-enterprise.spec.js +++ b/app/tests/import/collection-bundles-enterprise.spec.js @@ -66,7 +66,7 @@ describe('Collection Bundles API Full-Size Test', function () { .post('/api/collection-bundles?checkOnly=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -85,7 +85,7 @@ describe('Collection Bundles API Full-Size Test', function () { .post('/api/collection-bundles?previewOnly=true') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -104,7 +104,7 @@ describe('Collection Bundles API Full-Size Test', function () { .post('/api/collection-bundles') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -121,7 +121,7 @@ describe('Collection Bundles API Full-Size Test', function () { const res = await request(app) .get(`/api/stix-bundles?domain=${domain}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); diff --git a/app/tests/scheduler/scheduler.spec.js b/app/tests/scheduler/scheduler.spec.js index b0df0919..be90003b 100644 --- a/app/tests/scheduler/scheduler.spec.js +++ b/app/tests/scheduler/scheduler.spec.js @@ -528,7 +528,7 @@ describe('Scheduler', function () { .post('/api/collection-indexes') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); }); it('Scheduled job runs when initiated manually', async function () { diff --git a/app/tests/shared/login.js b/app/tests/shared/login.js index 3d5bd85b..b249133d 100644 --- a/app/tests/shared/login.js +++ b/app/tests/shared/login.js @@ -14,7 +14,9 @@ exports.loginAnonymous = async function (app) { // Save the cookie for later tests const cookies = setCookieParser(res); - const passportCookie = cookies.find((c) => c.name === passportCookieName); + // The cookie name may be 'connect.sid' or 'connect.XXXXXXXX.sid' depending on hostname + // Look for any cookie that matches the pattern connect*.sid + const passportCookie = cookies.find((c) => c.name.startsWith('connect.') && c.name.endsWith('.sid')); return passportCookie; }; diff --git a/app/tests/shared/pagination.js b/app/tests/shared/pagination.js index 62029948..8832d633 100644 --- a/app/tests/shared/pagination.js +++ b/app/tests/shared/pagination.js @@ -69,7 +69,7 @@ PaginationTests.prototype.executeTests = function () { const res = await request(app) .get(`${self.options.baseUrl}?offset=0&limit=10${self.options.stateQuery}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -84,7 +84,7 @@ PaginationTests.prototype.executeTests = function () { const res = await request(app) .get(`${self.options.baseUrl}?offset=10&limit=10${self.options.stateQuery}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -101,7 +101,7 @@ PaginationTests.prototype.executeTests = function () { `${self.options.baseUrl}?offset=0&limit=10&includePagination=true${self.options.stateQuery}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -125,7 +125,7 @@ PaginationTests.prototype.executeTests = function () { `${self.options.baseUrl}?offset=10&limit=10&includePagination=true${self.options.stateQuery}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -148,7 +148,7 @@ PaginationTests.prototype.executeTests = function () { const res = await request(app) .get(`${self.options.baseUrl}?offset=0${self.options.stateQuery}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/) .send(); @@ -169,7 +169,7 @@ PaginationTests.prototype.executeTests = function () { `${self.options.baseUrl}?offset=${offset}&limit=${pageSize}${self.options.stateQuery}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -186,7 +186,7 @@ PaginationTests.prototype.executeTests = function () { `${self.options.baseUrl}?offset=${offset}&limit=${pageSize}&includePagination=true${self.options.stateQuery}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -209,7 +209,7 @@ PaginationTests.prototype.executeTests = function () { const res = await request(app) .get(`${self.options.baseUrl}?offset=40&limit=20${self.options.stateQuery}`) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -226,7 +226,7 @@ PaginationTests.prototype.executeTests = function () { `${self.options.baseUrl}?offset=40&limit=20&includePagination=true${self.options.stateQuery}`, ) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); From 73ed17319a5e4b966cdeaf4edbf01d25fcee94ac Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 09:00:11 -0600 Subject: [PATCH 060/370] style: autoformat --- app/tests/shared/login.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/tests/shared/login.js b/app/tests/shared/login.js index b249133d..0c387162 100644 --- a/app/tests/shared/login.js +++ b/app/tests/shared/login.js @@ -16,7 +16,9 @@ exports.loginAnonymous = async function (app) { const cookies = setCookieParser(res); // The cookie name may be 'connect.sid' or 'connect.XXXXXXXX.sid' depending on hostname // Look for any cookie that matches the pattern connect*.sid - const passportCookie = cookies.find((c) => c.name.startsWith('connect.') && c.name.endsWith('.sid')); + const passportCookie = cookies.find( + (c) => c.name.startsWith('connect.') && c.name.endsWith('.sid'), + ); return passportCookie; }; From 811adb1fcdee4ae76f2431780565e5a5163a165c Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Sun, 9 Nov 2025 09:23:05 -0600 Subject: [PATCH 061/370] test: update cookie handling in authentication tests to support dynamic cookie names --- app/tests/authn/anonymous-authn.spec.js | 20 ++++++------------- app/tests/integration-test/initialize-data.js | 7 +++---- .../integration-test/update-subscription.js | 9 ++++----- app/tests/shared/login.js | 3 --- 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/app/tests/authn/anonymous-authn.spec.js b/app/tests/authn/anonymous-authn.spec.js index 2ee250f0..ca787598 100644 --- a/app/tests/authn/anonymous-authn.spec.js +++ b/app/tests/authn/anonymous-authn.spec.js @@ -1,15 +1,13 @@ const request = require('supertest'); const { expect } = require('expect'); -const setCookieParser = require('set-cookie-parser'); const database = require('../../lib/database-in-memory'); const databaseConfiguration = require('../../lib/database-configuration'); +const login = require('../shared/login'); const logger = require('../../lib/logger'); logger.level = 'debug'; -const passportCookieName = 'connect.sid'; - describe('Anonymous User Authentication', function () { let app; let passportCookie; @@ -34,14 +32,8 @@ describe('Anonymous User Authentication', function () { }); it('GET /api/authn/anonymous/login successfully logs the user in', async function () { - const response = await request(app) - .get('/api/authn/anonymous/login') - .set('Accept', 'application/json') - .expect(200); - - // Save the cookie for later tests - const cookies = setCookieParser(response); - passportCookie = cookies.find((c) => c.name === passportCookieName); + // Use the shared login helper + passportCookie = await login.loginAnonymous(app); expect(passportCookie).toBeDefined(); }); @@ -49,7 +41,7 @@ describe('Anonymous User Authentication', function () { const response = await request(app) .get('/api/session') .set('Accept', 'application/json') - .set('Cookie', `${passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); // We expect to get the current session @@ -61,7 +53,7 @@ describe('Anonymous User Authentication', function () { await request(app) .get('/api/authn/anonymous/logout') .set('Accept', 'application/json') - .set('Cookie', `${passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); }); @@ -69,7 +61,7 @@ describe('Anonymous User Authentication', function () { await request(app) .get('/api/session') .set('Accept', 'application/json') - .set('Cookie', `${passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(401); }); diff --git a/app/tests/integration-test/initialize-data.js b/app/tests/integration-test/initialize-data.js index b5ebbba8..623cbefe 100644 --- a/app/tests/integration-test/initialize-data.js +++ b/app/tests/integration-test/initialize-data.js @@ -5,19 +5,18 @@ const superagent = require('superagent'); const setCookieParser = require('set-cookie-parser'); -const passportCookieName = 'connect.sid'; - let passportCookie; async function login(url) { const res = await superagent.get(url); const cookies = setCookieParser(res); - passportCookie = cookies.find((c) => c.name === passportCookieName); + // The cookie name may be 'connect.sid' or 'connect.XXXXXXXX.sid' depending on hostname + passportCookie = cookies.find((c) => c.name.startsWith('connect.') && c.name.endsWith('.sid')); } function post(url, data) { return superagent .post(url) - .set('Cookie', `${passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .send(data); } diff --git a/app/tests/integration-test/update-subscription.js b/app/tests/integration-test/update-subscription.js index cf9a7e90..34ff21b8 100644 --- a/app/tests/integration-test/update-subscription.js +++ b/app/tests/integration-test/update-subscription.js @@ -5,19 +5,18 @@ const superagent = require('superagent'); const setCookieParser = require('set-cookie-parser'); -const passportCookieName = 'connect.sid'; - let passportCookie; async function login(url) { const res = await superagent.get(url); const cookies = setCookieParser(res); - passportCookie = cookies.find((c) => c.name === passportCookieName); + // The cookie name may be 'connect.sid' or 'connect.XXXXXXXX.sid' depending on hostname + passportCookie = cookies.find((c) => c.name.startsWith('connect.') && c.name.endsWith('.sid')); } async function get(url) { const res = await superagent .get(url) - .set('Cookie', `${passportCookieName}=${passportCookie.value}`); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); return res.body; } @@ -25,7 +24,7 @@ async function get(url) { function put(url, data) { return superagent .put(url) - .set('Cookie', `${passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .send(data); } diff --git a/app/tests/shared/login.js b/app/tests/shared/login.js index 0c387162..b9032640 100644 --- a/app/tests/shared/login.js +++ b/app/tests/shared/login.js @@ -3,9 +3,6 @@ const request = require('supertest'); const setCookieParser = require('set-cookie-parser'); -const passportCookieName = 'connect.sid'; -exports.passportCookieName = passportCookieName; - exports.loginAnonymous = async function (app) { const res = await request(app) .get('/api/authn/anonymous/login') From 3d94d54a7377be999cb95693363bf7516ea101f5 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 09:53:09 -0600 Subject: [PATCH 062/370] docs: add/update documentation about authentication --- docs/README.md | 9 + docs/authentication/README.md | 116 ++++ docs/authentication/authentik.md | 177 ++++++ docs/authentication/configuration.md | 186 ++++++ docs/authentication/keycloak.md | 300 ++++++++++ docs/authentication/okta.md | 311 ++++++++++ docs/authentication/testing-verification.md | 143 +++++ docs/configuration.md | 596 ++++++++++++++++++++ docs/legacy/authentication.md | 22 + docs/legacy/user-management.md | 89 ++- 10 files changed, 1934 insertions(+), 15 deletions(-) create mode 100644 docs/authentication/README.md create mode 100644 docs/authentication/authentik.md create mode 100644 docs/authentication/configuration.md create mode 100644 docs/authentication/keycloak.md create mode 100644 docs/authentication/okta.md create mode 100644 docs/authentication/testing-verification.md create mode 100644 docs/configuration.md diff --git a/docs/README.md b/docs/README.md index 9b150ce7..f21c0b99 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,15 @@ The following documents provide detailed technical information about specific as - [Data Model](data-model.md): Detailed explanation of the database schema and STIX object structure +## Authentication Configuration + +Comprehensive guides for configuring user authentication with various identity providers: + +- [Authentication Overview](authentication/README.md): Introduction and quick start guide +- [Authentik Setup](authentication/authentik.md): Step-by-step guide for Authentik configuration +- [Keycloak Setup](authentication/keycloak.md): Step-by-step guide for Keycloak configuration +- [Okta Setup](authentication/okta.md): Step-by-step guide for Okta configuration + ## Legacy Documentation The following documents contain additional information that may be useful for specific scenarios but are not part of the primary documentation: diff --git a/docs/authentication/README.md b/docs/authentication/README.md new file mode 100644 index 00000000..3b0b7e78 --- /dev/null +++ b/docs/authentication/README.md @@ -0,0 +1,116 @@ +# Authentication Configuration Guide + +This directory contains comprehensive guides for configuring user authentication with the ATT&CK Workbench REST API using OpenID Connect (OIDC). + +## Overview + +The ATT&CK Workbench REST API supports two user authentication mechanisms: + +- **Anonymous**: No authentication required (default, suitable for local development) +- **OIDC**: OpenID Connect authentication with an external identity provider (recommended for production) + +This documentation focuses on OIDC configuration with popular identity providers. + +## Supported Identity Providers + +The REST API is compatible with any OIDC-compliant identity provider. We provide detailed setup guides for: + +- [**Authentik**](authentik.md) - Open-source identity provider +- [**Keycloak**](keycloak.md) - Open-source identity and access management +- [**Okta**](okta.md) - Enterprise identity and access management service + +## Quick Start + +### Basic OIDC Configuration + +All OIDC providers require the following environment variables in your `.env` file: + +```bash +# Enable OIDC authentication +AUTHN_MECHANISM=oidc + +# OIDC provider settings +AUTHN_OIDC_ISSUER_URL= +AUTHN_OIDC_CLIENT_ID= +AUTHN_OIDC_CLIENT_SECRET= +AUTHN_OIDC_REDIRECT_ORIGIN= +``` + +### Required OIDC Scopes + +The REST API requires these OIDC scopes: + +- `openid` (required) +- `email` (required) +- `profile` (required) + +### Required Claims + +The REST API expects these claims in the ID token: + +- `email` - User's email address (used as unique identifier) +- `preferred_username` - Username +- `name` - User's display name + +## Multiple Environments + +If you're running multiple instances (e.g., local development and production), each instance needs its own `AUTHN_OIDC_REDIRECT_ORIGIN` value, but can share the same Client ID and Secret. + +**Example:** + +- **Local**: `AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000` +- **Production**: `AUTHN_OIDC_REDIRECT_ORIGIN=https://workbench.example.com` + +In your OIDC provider, configure **all** redirect URIs: + +- `http://localhost:3000/api/authn/oidc/callback` +- `https://workbench.example.com/api/authn/oidc/callback` + +## User Management + +After configuring OIDC, users who log in will be authenticated but will not have any permissions until you create a user account for them in the Workbench. + +### User Roles + +- `admin` - Full access to all features +- `editor` - Can create and edit objects +- `visitor` - Read-only access +- `team_lead` - Editor permissions with team management features + +### Creating User Accounts + +1. Have the user log in once (they will see "User not registered" message) +2. Create a user account via the REST API or frontend +3. Assign appropriate role +4. User logs in again and will have assigned permissions + +See [User Management](../legacy/user-management.md) for detailed instructions. + +## Troubleshooting + +### Common Issues + +#### "Invalid redirect URI" error + +- Verify the redirect URI in your OIDC provider exactly matches: `/api/authn/oidc/callback` +- Check that the protocol (http/https) matches + +#### "Invalid issuer" error + +- Ensure `AUTHN_OIDC_ISSUER_URL` is correct and accessible from the REST API server +- Verify the issuer URL includes the correct path (some providers require specific paths) + +#### User authenticated but has no permissions + +- Create a user account in the Workbench database +- Verify the email in the user account matches the email claim from the OIDC provider + +#### Claims missing from token + +- Check that your OIDC provider is configured to include `email`, `preferred_username`, and `name` in the ID token +- Verify the scopes include `openid`, `email`, and `profile` + +## Additional Resources + +- [Authentication Technical Details](../legacy/authentication.md) +- [User Management Guide](../legacy/user-management.md) diff --git a/docs/authentication/authentik.md b/docs/authentication/authentik.md new file mode 100644 index 00000000..57958f6f --- /dev/null +++ b/docs/authentication/authentik.md @@ -0,0 +1,177 @@ +# Authentik OIDC Configuration Guide + +> [!NOTE] +> **This guide is confirmed to be working as of November 10, 2025.** + +This guide provides step-by-step instructions for configuring Authentik as the OIDC identity provider for the ATT&CK Workbench REST API. + +## Prerequisites + +- Authentik server installed and accessible +- Administrator access to Authentik +- ATT&CK Workbench REST API installed + +## Overview + +This guide focuses on configuring Authentik as your OIDC provider. After completing Authentik setup: + +- Proceed to [REST API Configuration](./configuration.md) to configure the Workbench REST API +- Then follow [Testing & Verification](./testing-verification.md) to confirm everything works + +This guide covers only the Authentik-specific configuration steps. + +--- + +## Step 1: Create OAuth2/OpenID Provider + +1. **Log into Authentik** as an administrator + +2. **Navigate to Providers**: + - Go to **Applications** → **Providers** + - Click **Create** + +3. **Select Provider Type**: + - Choose **OAuth2/OpenID Provider** + +4. **Configure the Provider**: + - **Name**: `ATT&CK Workbench` (or your preferred name) + - **Authentication flow**: `default-authentication-flow` (or your custom flow) + - **Authorization flow**: `default-provider-authorization-explicit-consent` (recommended) or `default-provider-authorization-implicit-consent` + - **Client type**: `Confidential` (required) + - **Client ID**: Auto-generated (you'll need this later) + - **Client Secret**: Auto-generated (you'll need this later) + - **Redirect URIs/Origins (RegEx)**: Add your callback URL(s): + - For single environment: `https://workbench.example.com/api/authn/oidc/callback` + - For multiple environments, add each on a separate line: + + ```text + http://localhost:3000/api/authn/oidc/callback + https://workbench.example.com/api/authn/oidc/callback + ``` + + - **Signing Key**: `authentik Self-signed Certificate` (or your custom key) + +5. **Advanced Settings** (expand if needed): + - **Scopes**: Ensure these are included (usually default): + - `openid` + - `email` + - `profile` + - **Subject mode**: `Based on the User's hashed ID` (default is fine) + - **Include claims in id_token**: `true` (recommended) + +6. **Save** the provider + +7. **Note the credentials** (you'll need these for REST API configuration): + - Go back to the provider you just created + - Copy the **Client ID** + - Copy the **Client Secret** (click "Copy" button) + +## Step 2: Create Application + +1. **Navigate to Applications**: + - Go to **Applications** → **Applications** + - Click **Create** + +2. **Configure the Application**: + - **Name**: `ATT&CK Workbench` + - **Slug**: `attack-workbench` (or your preference) + - **Provider**: Select the provider you created in Step 1 + - **Policy engine mode**: `any` (or configure based on your needs) + - **UI settings** (optional): Add icon, description, launch URL + +3. **Save** the application + +## Step 3: Note the Issuer URL + +The issuer URL format for Authentik is: + +```text +https:///application/o// +``` + +For example: + +- If your Authentik is at: `https://authentik.example.com` +- And your application slug is: `attack-workbench` +- Then your issuer URL is: `https://authentik.example.com/application/o/attack-workbench/` + +**Note the trailing slash** - it's required! + +**Save this issuer URL** - you'll need it for REST API configuration. + +--- + +## Next Steps + +You've completed the Authentik configuration. Now proceed with: + +1. **[Configure the REST API](./configuration.md)** - Set up the Workbench REST API to use Authentik + + You'll need these values from the steps above: + - **Issuer URL**: `https:///application/o//` (from Step 3) + - **Client ID**: From Step 1 + - **Client Secret**: From Step 1 + +2. **[Test & Verify](./testing-verification.md)** - Confirm authentication is working correctly + +--- + +## Troubleshooting + +### Authentik Issuer URL Format + +**Issue**: Discovery fails with Authentik. + +**Solution**: Verify the issuer URL format is correct: + +```text +https:///application/o// +``` + +**Important notes:** + +- The trailing slash is **required** +- The application slug must match exactly (case-sensitive) +- Verify by accessing: `https:///.well-known/openid-configuration` + +### Authentik Scope Configuration + +**Issue**: Missing user information after authentication. + +**Solution**: In Authentik provider settings, ensure: + +- **Scopes** include: `openid`, `email`, `profile` +- "Include claims in id_token" is enabled in Advanced Settings +- Users have email addresses configured in Authentik + +## Advanced Configuration + +### Custom User Attributes + +Authentik supports custom user attributes. To use them with Workbench: + +1. Create a custom property mapping in Authentik +2. Add it to your provider's scope mappings +3. The claims will be available in the OIDC token + +### MFA / 2FA + +Authentik supports Multi-Factor Authentication: + +1. Configure MFA in Authentik authentication flow +2. No changes needed in Workbench REST API +3. Users will be prompted for MFA during Authentik login + +### Single Logout + +Currently, logging out of Workbench only logs the user out of the REST API session, not from Authentik. Users remain logged into Authentik and can re-authenticate without entering credentials. + +To implement full logout, you would need to: + +1. Redirect to Authentik's end session endpoint after logout +2. This requires custom frontend modifications + +## Additional Resources + +- [Authentik Documentation](https://docs.goauthentik.io/) +- [ATT&CK Workbench Authentication Documentation](./README.md) diff --git a/docs/authentication/configuration.md b/docs/authentication/configuration.md new file mode 100644 index 00000000..6d4fe778 --- /dev/null +++ b/docs/authentication/configuration.md @@ -0,0 +1,186 @@ +# REST API OIDC Configuration + +This guide describes how to configure the ATT&CK Workbench REST API to use an OpenID Connect (OIDC) identity provider for authentication. + +## Prerequisites + +Before configuring the REST API, ensure you have: + +1. Completed the OIDC provider setup (Authentik, Okta, Keycloak, or other provider) +2. Obtained the following values from your identity provider: + - **Issuer URL**: The OIDC issuer/discovery endpoint + - **Client ID**: The application/client identifier + - **Client Secret**: The confidential client secret +3. Determined your **Redirect Origin**: The base URL where users access your Workbench instance + +## Configuration Steps + +### Step 1: Edit Environment Configuration + +Edit the `.env` file in your REST API installation directory and add the following OIDC settings: + +```bash +# Enable OIDC authentication +AUTHN_MECHANISM=oidc + +# OIDC Provider settings +AUTHN_OIDC_ISSUER_URL= +AUTHN_OIDC_CLIENT_ID= +AUTHN_OIDC_CLIENT_SECRET= +AUTHN_OIDC_REDIRECT_ORIGIN= +``` + +#### Configuration Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `AUTHN_MECHANISM` | Authentication method to use | `oidc` | +| `AUTHN_OIDC_ISSUER_URL` | OIDC discovery endpoint from your provider | `https://auth.example.com/realms/workbench` | +| `AUTHN_OIDC_CLIENT_ID` | Client ID from your OIDC application | `attack-workbench-rest-api` | +| `AUTHN_OIDC_CLIENT_SECRET` | Client secret from your OIDC application | `your-secret-value` | +| `AUTHN_OIDC_REDIRECT_ORIGIN` | Base URL where users access Workbench | `https://workbench.example.com` | + +**Important Notes:** + +- The callback URL is automatically constructed as: `{AUTHN_OIDC_REDIRECT_ORIGIN}/api/authn/oidc/callback` +- Ensure this callback URL matches exactly what you configured in your OIDC provider +- The issuer URL format varies by provider - see your provider's specific guide + +### Step 2: Multiple Environment Configuration + +When deploying to multiple environments (development, staging, production), each instance needs its own `.env` file with environment-specific values. + +**Local Development** (`.env`): + +```bash +AUTHN_MECHANISM=oidc +AUTHN_OIDC_ISSUER_URL=https://auth.example.com/realms/workbench +AUTHN_OIDC_CLIENT_ID=attack-workbench-rest-api +AUTHN_OIDC_CLIENT_SECRET=your-client-secret +AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000 +``` + +**Production** (`.env`): + +```bash +AUTHN_MECHANISM=oidc +AUTHN_OIDC_ISSUER_URL=https://auth.example.com/realms/workbench +AUTHN_OIDC_CLIENT_ID=attack-workbench-rest-api +AUTHN_OIDC_CLIENT_SECRET=your-client-secret +AUTHN_OIDC_REDIRECT_ORIGIN=https://workbench.example.com +``` + +**Best Practices:** + +- Use the same Client ID and Secret across environments (configure multiple redirect URIs in your provider) +- Use environment variables or secrets management for Client Secret in production +- Never commit the `.env` file to version control +- Keep a `.env.template` file with dummy values for documentation + +### Step 3: Restart the REST API + +After updating the configuration, restart the REST API to load the new settings: + +```bash +# If using Docker Compose +docker compose restart rest-api + +# If running directly with npm +npm start +``` + +### Step 4: Verify Configuration Load + +Check the REST API logs during startup to confirm the configuration was loaded successfully: + +```bash +# If using Docker Compose +docker compose logs rest-api + +# If running directly +# Check your console output or log files +``` + +Look for messages indicating: + +- OIDC authentication enabled +- Connection to the issuer successful +- Discovery endpoint loaded + +## Configuration File Alternative + +Instead of environment variables, you can use a JSON configuration file. This is useful for: + +- Managing multiple configuration sections in one place +- Version controlling your configuration (without secrets) +- Configuring complex structures like service accounts + +**Using JSON Configuration:** + +1. Create a `config.json` file: + + ```json + { + "userAuthn": { + "mechanism": "oidc", + "oidc": { + "issuerUrl": "https://auth.example.com/realms/workbench", + "clientId": "attack-workbench-rest-api", + "clientSecret": "your-client-secret", + "redirectOrigin": "https://workbench.example.com" + } + } + } + ``` + +2. Reference it via environment variable: + + ```bash + JSON_CONFIG_PATH=/path/to/config.json + ``` + +**Configuration Precedence:** + +When both environment variables and JSON configuration are used: + +1. Environment variables are loaded first +2. JSON configuration file (if specified) overrides environment variables + +For complete configuration documentation, including all available options and advanced scenarios, +see the [REST API Configuration Guide](../configuration.md). + +## Next Steps + +After configuring the REST API, proceed to [Testing & Verification](./testing-verification.md) to confirm your authentication setup is working correctly. + +## Troubleshooting + +### Configuration not loading + +**Symptoms**: REST API still shows anonymous authentication + +**Solutions**: + +1. Verify the `.env` file is in the correct directory (REST API root) +2. Check for typos in variable names (they are case-sensitive) +3. Ensure there are no spaces around the `=` sign +4. Restart the REST API after making changes + +### Invalid configuration values + +**Symptoms**: Errors during REST API startup + +**Solutions**: + +1. Verify the issuer URL is correct and accessible from the REST API server +2. Check that Client ID and Secret match your OIDC provider configuration +3. Ensure the redirect origin URL is correct (no trailing slash) + +## Additional Resources + +- [Authentication Overview](./README.md) +- [Testing & Verification Guide](./testing-verification.md) +- Provider-specific guides: + - [Authentik Configuration](./authentik.md) + - [Okta Configuration](./okta.md) + - [Keycloak Configuration](./keycloak.md) diff --git a/docs/authentication/keycloak.md b/docs/authentication/keycloak.md new file mode 100644 index 00000000..01abd77e --- /dev/null +++ b/docs/authentication/keycloak.md @@ -0,0 +1,300 @@ +# Keycloak OIDC Configuration Guide + +> [!WARNING] +> This guide is in draft mode. Any feedback is appreciated! + +This guide provides step-by-step instructions for configuring Keycloak as the OIDC identity provider for the ATT&CK Workbench REST API. + +## Prerequisites + +- Keycloak server installed and accessible +- Administrator access to Keycloak Admin Console +- ATT&CK Workbench REST API installed + +## Overview + +This guide focuses on configuring Keycloak as your OIDC provider. After completing Keycloak setup: + +- Proceed to [REST API Configuration](./configuration.md) to configure the Workbench REST API +- Then follow [Testing & Verification](./testing-verification.md) to confirm everything works + +This guide covers only the Keycloak-specific configuration steps. + +--- + +## Step 1: Create a Realm (Optional) + +You can use an existing realm or create a new one for the Workbench. + +1. **Log into Keycloak Admin Console** as an administrator + +2. **Create a new realm** (or skip to use an existing one): + - Hover over the realm name in the top-left corner + - Click **Add Realm** (or **Create Realm** in newer versions) + - **Name**: `workbench-realm` (or your preferred name) + - Click **Create** + +## Step 2: Create OIDC Client + +1. **Navigate to Clients**: + - In your realm, go to **Clients** + - Click **Create** (or **Create client**) + +2. **General Settings**: + - **Client type**: `OpenID Connect` + - **Client ID**: `attack-workbench-rest-api` (or your preferred ID) + - **Name**: `ATT&CK Workbench REST API` (optional, for display) + - **Description**: `OIDC client for ATT&CK Workbench` (optional) + - Click **Next** or **Save** + +3. **Capability Config** (if prompted): + - **Client authentication**: `ON` (required - this makes it a confidential client) + - **Authorization**: `OFF` + - **Authentication flow**: + - ✓ **Standard flow** (required - this enables the authorization code flow) + - ✗ Direct access grants (not needed) + - ✗ Implicit flow (not recommended) + - Click **Next** or **Save** + +4. **Settings Tab**: + - **Client authentication**: `ON` + - **Root URL**: Leave empty or set to your Workbench URL + - **Valid Redirect URIs**: Add your callback URL(s): + - For single environment: `https://workbench.example.com/api/authn/oidc/callback` + - For multiple environments, add each separately: + + ```text + http://localhost:3000/api/authn/oidc/callback + https://workbench.example.com/api/authn/oidc/callback + ``` + + - **Valid post logout redirect URIs**: `+` (allows any valid redirect URI) + - **Web origins**: `+` (allows CORS for valid redirect URIs) or specify explicitly: + + ```text + http://localhost:3000 + https://workbench.example.com + ``` + + - Click **Save** + +5. **Get Client Credentials**: + - Go to the **Credentials** tab + - Copy the **Client Secret** + - Note: The Client ID is what you set in step 2 + +## Step 3: Configure Client Scopes + +The default scopes should work, but verify they're configured: + +1. **Navigate to Client Scopes** (in your realm) + +2. **Verify these scopes exist**: + - `openid` (required) + - `email` (required) + - `profile` (required) + +3. **Check the client's scopes**: + - Go back to your client + - Go to **Client Scopes** tab + - Verify `email` and `profile` are in **Assigned Default Client Scopes** + - `openid` is automatically included + +## Step 4: Create Users + +1. **Navigate to Users**: + - In your realm, go to **Users** + - Click **Add user** (or **Create new user**) + +2. **User Details**: + - **Username**: `admin@example.com` (or your preferred username) + - **Email**: `admin@example.com` (required) + - **Email verified**: `ON` (recommended) + - **First name**: `Admin` + - **Last name**: `User` + - Click **Create** + +3. **Set Password**: + - Go to the **Credentials** tab + - Click **Set password** + - Enter a password + - **Temporary**: `OFF` (if you don't want to force password reset) + - Click **Save** + +4. **Repeat** for additional users (editor, visitor, etc.) + +## Step 5: Get Issuer URL + +The issuer URL format for Keycloak is: + +```text +https:///realms/ +``` + +For example: + +- If your Keycloak is at: `https://keycloak.example.com` +- And your realm is: `workbench-realm` +- Then your issuer URL is: `https://keycloak.example.com/realms/workbench-realm` + +You can verify this by navigating to: + +```text +https://keycloak.example.com/realms/workbench-realm/.well-known/openid-configuration +``` + +This should return the OpenID Connect discovery document. + +--- + +## Next Steps + +You've completed the Keycloak configuration. Now proceed with: + +1. **[Configure the REST API](./configuration.md)** - Set up the Workbench REST API to use Keycloak + + You'll need these values from the steps above: + - **Issuer URL**: `https:///realms/` (from Step 5) + - **Client ID**: From Step 2 + - **Client Secret**: From Step 2 + +2. **[Test & Verify](./testing-verification.md)** - Confirm authentication is working correctly + +--- + +## Automated Configuration Script + +For development/testing environments, the REST API repository includes a configuration script: + +```bash +node ./scripts/configureKeycloak.js +``` + +This script: + +- Creates a test realm (`test-oidc-realm`) +- Creates a client (`attack-workbench-rest-api`) +- Creates test users with passwords +- Creates corresponding user accounts in Workbench + +**Note**: This is intended for development only. Do not use in production. + +## Troubleshooting + +### Keycloak Issuer URL Format + +**Issue**: Discovery fails with Keycloak. + +**Solution**: Verify the issuer URL format is correct: + +```text +https:///realms/ +``` + +Test the discovery endpoint manually: + +```bash +curl https://keycloak.example.com/realms/workbench-realm/.well-known/openid-configuration +``` + +Important notes: + +- Ensure the realm name is spelled correctly (case-sensitive) +- No trailing slash after the realm name +- The realm must exist and be active in Keycloak + +### Keycloak Client Authentication + +**Issue**: "Unauthorized client" error during authentication. + +**Solution**: Verify client configuration in Keycloak: + +1. Go to your client's **Settings** tab +2. Ensure "Client authentication" is **ON** (makes it a confidential client) +3. Verify you're using the correct Client Secret from the **Credentials** tab +4. Ensure "Standard flow" is enabled in the client settings + +### Keycloak Scope Configuration + +**Issue**: Missing user information after authentication. + +**Solution**: Ensure scopes are properly assigned: + +1. Go to your client → **Client Scopes** tab +2. Verify `email` and `profile` are in **Assigned Default Client Scopes** +3. `openid` scope is automatically included +4. Check that Keycloak users have email addresses configured +5. Verify "Email verified" is ON for users (or configure client to not require it) + +### Keycloak Redirect URI Support + +**Issue**: "Invalid redirect URI" error. + +**Solution**: Keycloak supports wildcard patterns: + +1. Check the client's "Valid Redirect URIs" setting +2. You can use wildcards like `http://localhost:*` for development +3. For production, use exact URIs for better security + +## Advanced Configuration + +### Service Account Authentication + +Keycloak supports OIDC Client Credentials flow for service-to-service authentication. + +1. **Enable service account** in your client: + - Go to **Settings** tab + - Enable **Service accounts roles** + - Save + +2. **Configure in REST API**: + + ```bash + # In .env file + SERVICE_ACCOUNT_OIDC_ENABLE=true + JWKS_URI=https://keycloak.example.com/realms/workbench-realm/protocol/openid-connect/certs + ``` + +3. **Add to JSON config** file: + + ```json + { + "serviceAuthn": { + "oidcClientCredentials": { + "enable": true, + "clients": [ + { + "clientId": "collection-manager-service", + "serviceRole": "collection-manager" + } + ] + } + } + } + ``` + +See [sample configuration](../../resources/sample-configurations/collection-manager-oidc-keycloak.json) for reference. + +### Custom Claims and Mappers + +Keycloak supports protocol mappers to add custom claims: + +1. In your client, go to **Client scopes** tab +2. Select a scope or create a dedicated scope +3. Click **Add mapper** → **By configuration** +4. Choose mapper type (User Attribute, User Property, etc.) +5. Configure the mapper to add claims to the ID token + +### Group/Role Mapping + +To map Keycloak roles to Workbench permissions: + +1. Create roles in Keycloak +2. Assign roles to users +3. Create a mapper to include roles in the token +4. Modify Workbench code to read and apply roles (requires custom development) + +## Additional Resources + +- [Keycloak Documentation](https://www.keycloak.org/documentation) +- [ATT&CK Workbench Authentication Documentation](./README.md) diff --git a/docs/authentication/okta.md b/docs/authentication/okta.md new file mode 100644 index 00000000..d554a209 --- /dev/null +++ b/docs/authentication/okta.md @@ -0,0 +1,311 @@ +# Okta OIDC Configuration Guide + +> [!WARNING] +> This guide is in draft mode. Any feedback is appreciated! + +This guide provides step-by-step instructions for configuring Okta as the OIDC identity provider for the ATT&CK Workbench REST API. + +## Prerequisites + +- Okta account (free Developer account or enterprise) +- Administrator access to Okta Admin Console +- ATT&CK Workbench REST API installed + +## Overview + +This guide focuses on configuring Okta as your OIDC provider. After completing Okta setup: + +- Proceed to [REST API Configuration](./configuration.md) to configure the Workbench REST API +- Then follow [Testing & Verification](./testing-verification.md) to confirm everything works + +This guide covers only the Okta-specific configuration steps. + +--- + +## Step 1: Create OIDC Application in Okta + +1. **Log into Okta Admin Console**: + - Navigate to your Okta domain (e.g., `https://dev-12345.okta.com/admin`) + - Sign in as an administrator + +2. **Navigate to Applications**: + - In the Admin Console, go to **Applications** → **Applications** + - Click **Create App Integration** + +3. **Select Sign-in Method**: + - **Sign-in method**: `OIDC - OpenID Connect` + - **Application type**: `Web Application` + - Click **Next** + +4. **Configure General Settings**: + - **App integration name**: `ATT&CK Workbench REST API` + - **Logo** (optional): Upload a logo if desired + - Click **Next** or continue to Grant type + +## Step 2: Configure Application Settings + +1. **Grant Type**: + - ✓ **Authorization Code** (required) + - ✗ Refresh Token (optional, not required) + - ✗ Implicit (not recommended) + +2. **Sign-in redirect URIs**: Add your callback URL(s): + - For single environment: + + ```text + https://workbench.example.com/api/authn/oidc/callback + ``` + + - For multiple environments, add each separately: + + ```text + http://localhost:3000/api/authn/oidc/callback + https://workbench.example.com/api/authn/oidc/callback + ``` + +3. **Sign-out redirect URIs** (optional): + - Add your application's base URLs: + + ```text + http://localhost:3000 + https://workbench.example.com + ``` + +4. **Controlled access** (Assignments): + - **Allow everyone in your organization to access**: For initial setup/testing + - **Limit access to selected groups**: For production (recommended) + - Select the appropriate option for your needs + +5. **Click Save** + +## Step 3: Get Client Credentials + +After saving, you'll be taken to the application details page: + +1. **Note the following values**: + - **Client ID**: Found under "Client Credentials" + - **Client Secret**: Click to reveal and copy + - **Okta domain**: Your Okta domain (e.g., `dev-12345.okta.com`) + +2. **Determine your Issuer URL**: + - For Okta Developer accounts: `https:///oauth2/default` + - For custom authorization servers: `https:///oauth2/` + - For org authorization server: `https://` + + **To find your issuer URL**: + - Go to **Security** → **API** in the Okta Admin Console + - Find your authorization server (typically "default" for developer accounts) + - Copy the **Issuer URI** + +## Step 4: Configure OpenID Connect Scopes + +1. **Navigate to your Authorization Server**: + - Go to **Security** → **API** + - Click on your authorization server (e.g., "default") + +2. **Verify Scopes**: + - Go to the **Scopes** tab + - Ensure these scopes exist and are enabled: + - `openid` (required) + - `email` (required) + - `profile` (required) + +3. **Configure Claims** (verify defaults): + - Go to the **Claims** tab + - Verify these claims are configured for ID Token: + - `sub` (subject - default) + - `email` (from user.email) + - `preferred_username` (from user.login or user.email) + - `name` (from user.displayName or concatenated firstName/lastName) + + If missing, add them: + - Click **Add Claim** + - **Name**: `email` + - **Include in token type**: `ID Token`, `Always` + - **Value type**: `Expression` + - **Value**: `user.email` + - Save + +## Step 5: Create or Assign Users + +### Option A: Create New Users + +1. **Navigate to Users**: + - Go to **Directory** → **People** + - Click **Add Person** + +2. **Fill in User Details**: + - **First name**: `Admin` + - **Last name**: `User` + - **Username**: `admin@example.com` + - **Primary email**: `admin@example.com` + - **Password**: Choose how to set: + - Set by admin + - Set by user (email activation) + - Click **Save** + +3. **Assign to Application**: + - On the user's profile, go to **Applications** tab + - Click **Assign Applications** + - Find "ATT&CK Workbench REST API" + - Click **Assign** → **Save and Go Back** + +### Option B: Assign Existing Users + +1. **From the Application**: + - Go to **Applications** → **Applications** + - Click on "ATT&CK Workbench REST API" + - Go to **Assignments** tab + - Click **Assign** → **Assign to People** or **Assign to Groups** + - Select users/groups and click **Assign** + +--- + +## Next Steps + +You've completed the Okta configuration. Now proceed with: + +1. **[Configure the REST API](./configuration.md)** - Set up the Workbench REST API to use Okta + + You'll need these values from the steps above: + - **Issuer URL**: From Step 3 (e.g., `https://dev-12345.okta.com/oauth2/default`) + - **Client ID**: From Step 3 + - **Client Secret**: From Step 3 + +2. **[Test & Verify](./testing-verification.md)** - Confirm authentication is working correctly + +--- + +## Troubleshooting + +### Okta Issuer URL Format + +**Issue**: Discovery fails with Okta. + +**Solution**: Verify the issuer URL format is correct for your Okta setup: + +- **With authorization server**: `https:///oauth2/` +- **Default auth server**: `https:///oauth2/default` +- **Org auth server**: `https://` + +Test the discovery endpoint manually: + +```bash +curl https://dev-12345.okta.com/oauth2/default/.well-known/openid-configuration +``` + +### Okta User Assignment + +**Issue**: Error "User is not assigned to the client application" during authentication. + +**Solution**: Okta requires explicit user/group assignment to applications: + +1. Go to your application in Okta Admin Console +2. Go to **Assignments** tab +3. Assign the user or their group to the application + +### Okta Claims Configuration + +**Issue**: Missing user information after authentication. + +**Solution**: Ensure claims are properly configured in Okta: + +1. Go to **Security** → **API** → your authorization server → **Claims** tab +2. Verify claims for `email`, `preferred_username`, and `name` exist +3. Ensure claims are configured to be included in ID Token (not just Access Token) +4. Check that Okta users have email addresses configured in their profiles + +### Okta Redirect URI Restrictions + +**Issue**: "Invalid redirect URI" error. + +**Solution**: Okta requires exact URI matches (no wildcards): + +1. In Okta application settings, check "Sign-in redirect URIs" +2. URIs must be exact matches - no wildcard patterns allowed +3. Add each environment's callback URL separately + +## Advanced Configuration + +### Service Account Authentication + +Okta supports Client Credentials flow for service-to-service authentication. + +1. **Create a Machine-to-Machine application**: + - In Okta, create a new app integration + - Choose **API Services** application type + - Note the Client ID and Client Secret + +2. **Configure in REST API**: + + ```bash + # In .env file + SERVICE_ACCOUNT_OIDC_ENABLE=true + JWKS_URI=https://dev-12345.okta.com/oauth2/default/v1/keys + ``` + +3. **Add to JSON config** file: + + ```json + { + "serviceAuthn": { + "oidcClientCredentials": { + "enable": true, + "clients": [ + { + "clientId": "0oa3xb9oz3QLY1avc5d7", + "serviceRole": "collection-manager" + } + ] + } + } + } + ``` + +See [sample configuration](../../resources/sample-configurations/collection-manager-oidc-okta.json) for reference. + +### Custom Authorization Server + +For production deployments, consider creating a custom authorization server: + +1. Go to **Security** → **API** +2. Click **Add Authorization Server** +3. Configure your custom authorization server +4. Use the custom server's Issuer URI in your configuration + +Benefits: + +- Isolated from default server +- Custom claims and scopes +- Better control over token lifetimes +- Separate audience values + +### Group-Based Access Control + +To restrict access based on Okta groups: + +1. **Create groups** in Okta (**Directory** → **Groups**) +2. **Assign users** to groups +3. **Configure application assignment**: + - Assign groups to your application instead of individual users +4. **Add group claim** to tokens (optional): + - Go to your authorization server → **Claims** + - Add a `groups` claim to include group memberships in tokens +5. **Custom logic** in Workbench to read and apply groups (requires code modification) + +### Multi-Factor Authentication (MFA) + +Okta supports comprehensive MFA options: + +1. **Configure Sign-On Policy**: + - In your application, go to **Sign On** tab + - Edit or create a sign-on policy + - Add a rule that requires MFA +2. **No changes needed** in Workbench +3. Users will be prompted for MFA during Okta login + +## Additional Resources + +- [Okta Developer Documentation](https://developer.okta.com/) +- [Okta OpenID Connect](https://developer.okta.com/docs/concepts/oauth-openid/) +- [ATT&CK Workbench Authentication Documentation](./README.md) diff --git a/docs/authentication/testing-verification.md b/docs/authentication/testing-verification.md new file mode 100644 index 00000000..a58da3a5 --- /dev/null +++ b/docs/authentication/testing-verification.md @@ -0,0 +1,143 @@ +# Testing & Verification + +This guide provides steps to verify your OIDC authentication configuration is working correctly with the ATT&CK Workbench REST API. + +## Prerequisites + +Before testing, ensure you have: + +1. Completed OIDC provider configuration (Authentik, Okta, Keycloak, etc.) +2. Configured the REST API with OIDC settings +3. Restarted the REST API +4. Created at least one user in your OIDC provider + +## Verification Steps + +### Step 1: Check REST API Logs + +When the REST API starts, it should log information about the authentication configuration. + +**View logs:** + +```bash +# If using Docker Compose +docker compose logs rest-api + +# If running directly +# Check your console output or log files +``` + +### Step 2: Test Configuration Endpoint + +The REST API exposes an endpoint that returns the configured authentication mechanism. + +**Test the endpoint:** + +```bash +curl http://localhost:3000/api/config/authn +``` + +**Expected response:** + +```json +{ + "mechanisms": [{"authnType":"oidc"}] +} +``` + +**If you see a different response:** + +- `{"mechanisms":[{"authnType":"anonymous"}]}` - OIDC is not enabled; check your configuration +- Error or timeout - REST API is not running or not accessible + +### Step 3: Test Authentication Flow + +Now test the complete authentication flow from the frontend. + +1. **Navigate to the Workbench frontend** in your browser: + - If running locally: + - If deployed: Your Workbench URL (e.g., ) + +2. **Click "Log In"** (or navigate to the login page) + +3. **Observe the redirect**: + - You should be automatically redirected to your OIDC provider's login page + - The URL should match your provider's domain (not the Workbench domain) + +4. **Log in with credentials**: + - Enter the username and password for a user in your OIDC provider + - Complete any MFA/2FA prompts if configured + +5. **Observe the callback**: + - After successful authentication, you should be redirected back to the Workbench + - The URL should temporarily show `/api/authn/oidc/callback` before redirecting to the main page + +6. **Verify authenticated state**: + - You should now be logged into the Workbench + - Your username should appear in the navigation bar + - You should have access based on your user's role + +### Step 4: Test Logout + +Test the logout functionality: + +1. **Click your username** in the navigation bar +2. **Select "Logout"** +3. **Verify:** + - You are logged out of the Workbench + - Attempting to access protected pages redirects you to login + - Note: You may still be logged into your OIDC provider (single logout varies by provider) + +## Common Issues and Solutions + +### Issue: "Users authenticated but have no permissions" + +**Symptoms:** + +- Users can log in successfully +- Users cannot view or edit any content +- Error messages about insufficient permissions + +**Cause:** User accounts exist in OIDC provider but not in the Workbench database. + +**Solutions:** + +1. **Create user accounts in Workbench:** + - OIDC only handles authentication, not authorization + - You must create corresponding user accounts in the Workbench database + - See the [User Management documentation](./README.md#user-management) for details + +2. **Verify username matching:** + - The username in Workbench must match the OIDC claim (usually `preferred_username` or `email`) + - Check the REST API logs to see what username is being extracted from the OIDC token + +## Debugging Tips + +### Test with curl + +You can test the OIDC endpoints directly: + +```bash +# Test the auth initiation endpoint +curl -v http://localhost:3000/api/authn/oidc/login + +# This should return a redirect (302) to your OIDC provider +``` + +## Next Steps + +Once authentication is working correctly: + +1. **Set up user accounts** - Create users in the Workbench database with appropriate roles +2. **Configure authorization** - Set up role-based access control +3. **Review security settings** - Ensure production-ready security configuration +4. **Set up monitoring** - Monitor authentication failures and session issues + +## Additional Resources + +- [Authentication Overview](./README.md) +- [REST API Configuration](./configuration.md) +- Provider-specific guides: + - [Authentik Configuration](./authentik.md) + - [Okta Configuration](./okta.md) + - [Keycloak Configuration](./keycloak.md) diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..d65345cd --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,596 @@ +# ATT&CK Workbench REST API Configuration Guide + +This guide explains how to configure the ATT&CK Workbench REST API using environment variables, +JSON configuration files, or a combination of both. + +## Table of Contents + +- [ATT\&CK Workbench REST API Configuration Guide](#attck-workbench-rest-api-configuration-guide) + - [Table of Contents](#table-of-contents) + - [Configuration System Overview](#configuration-system-overview) + - [Configuration Methods](#configuration-methods) + - [Environment Variables](#environment-variables) + - [JSON Configuration File](#json-configuration-file) + - [Configuration Precedence](#configuration-precedence) + - [Configuration Options](#configuration-options) + - [Server](#server) + - [Database](#database) + - [Application](#application) + - [Logging](#logging) + - [Session](#session) + - [User Authentication](#user-authentication) + - [OIDC Configuration](#oidc-configuration) + - [Service Authentication](#service-authentication) + - [OIDC Client Credentials](#oidc-client-credentials) + - [Challenge API Key](#challenge-api-key) + - [Basic API Key](#basic-api-key) + - [Multiple Service Authentication Methods](#multiple-service-authentication-methods) + - [Scheduler](#scheduler) + - [Collection Indexes](#collection-indexes) + - [Configuration Files](#configuration-files) + - [ATT\&CK Specific](#attck-specific) + - [Additional Resources](#additional-resources) + +--- + +## Configuration System Overview + +The REST API uses [Convict](https://github.com/mozilla/node-convict), a configuration management library. +All configuration is defined in `app/config/config.js` with sensible defaults. +You only need to override values specific to your environment. + +--- + +## Configuration Methods + +### Environment Variables + +Environment variables are the recommended method for configuring the REST API in containerized deployments and for simple configurations. + +A `template.env` file is included in the repository for you to make use of this option. + +### JSON Configuration File + +JSON configuration files are recommended for complex configurations, especially when defining service accounts or OIDC clients. + +**Setting up JSON configuration:** + +1. Create a configuration file (e.g., `config.json`): + + ```json + { + "server": { + "port": 3000, + "corsAllowedOrigins": ["https://workbench.example.com", "https://staging.example.com"] + }, + "database": { + "url": "mongodb://localhost:27017/attack-workspace" + }, + "userAuthn": { + "mechanism": "oidc", + "oidc": { + "issuerUrl": "https://auth.example.com/realms/workbench", + "clientId": "attack-workbench-rest-api", + "clientSecret": "your-client-secret", + "redirectOrigin": "https://workbench.example.com" + } + }, + "serviceAuthn": { + "basicApikey": { + "enable": true, + "serviceAccounts": [ + { + "name": "navigator", + "apikey": "your-navigator-apikey", + "serviceRole": "read-only" + }, + { + "name": "collection-manager", + "apikey": "your-collection-manager-apikey", + "serviceRole": "collection-manager" + } + ] + } + } + } + ``` + +2. Reference the file via environment variable: + + ```bash + export JSON_CONFIG_PATH=/path/to/config.json + npm start + ``` + + Or in `.env`: + + ```bash + JSON_CONFIG_PATH=/path/to/config.json + ``` + +### Configuration Precedence + +When both environment variables and JSON configuration are used: + +1. **Environment variables** are loaded first with their defaults +2. **JSON configuration file** (if specified) is loaded second and overrides environment variables +3. **Validation** occurs after all configuration is loaded + +**Example:** + +```bash +# .env file +PORT=3000 +DATABASE_URL=mongodb://localhost:27017/attack-workspace +JSON_CONFIG_PATH=./config.json +``` + +```json +// config.json +{ + "server": { + "port": 8080 + } +} +``` + +**Result:** Port will be `8080` (JSON overrides environment variable) + +--- + +## Configuration Options + +### Server + +Configuration for the HTTP server. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|----------------------|-------------------------|----------------------------|---------|---------|-----------------------------------------------------------------------------------------------------------------| +| Port | `PORT` | `server.port` | integer | `3000` | HTTP server port | +| CORS Allowed Origins | `CORS_ALLOWED_ORIGINS` | `server.corsAllowedOrigins`| domains | `*` | Allowed origins for CORS. Use `*` for all, `disable` to disable CORS, or comma-separated list of origins | + +**CORS Allowed Origins** accepts: + +- `*` - Allow any origin (not recommended for production) +- `disable` - Disable CORS entirely +- Comma-separated list of origins (with protocol): + - `https://workbench.example.com` + - `http://localhost:4200,https://workbench.example.com` +- Supports localhost, private IPs (10.x, 172.16-31.x, 192.168.x), and FQDNs + +**Examples:** + +```bash +# Environment variable +CORS_ALLOWED_ORIGINS=https://workbench.example.com,https://staging.example.com +``` + +```json +// JSON +{ + "server": { + "corsAllowedOrigins": ["https://workbench.example.com", "https://staging.example.com"] + } +} +``` + +### Database + +MongoDB database configuration. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|--------------|-------------------------------------|------------------------------|---------|-------------|-------------------------------------------| +| URL | `DATABASE_URL` | `database.url` | string | *(empty)* | MongoDB connection string (REQUIRED) | +| Auto-migrate | `WB_REST_DATABASE_MIGRATION_ENABLE` | `database.migration.enable` | boolean | `true` | Run migrations automatically on startup | + +**Examples:** + +```bash +# Local MongoDB +DATABASE_URL=mongodb://localhost:27017/attack-workspace + +# Docker Compose +DATABASE_URL=mongodb://attack-workbench-database/attack-workspace +``` + +**Migration Notes:** + +- When `database.migration.enable` is `true`, migrations run automatically at startup +- Set to `false` if you manage migrations separately (e.g., in a Kubernetes init container) +- Migrations are idempotent and safe to run multiple times + +### Application + +General application settings. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|---------------------|----------------------|-------------------------|--------|-----------------------------|-----------------------------------------------------------| +| Name | *(none)* | `app.name` | string | `attack-workbench-rest-api` | Application name | +| Environment | `NODE_ENV` | `app.env` | string | `development` | Environment name (`development`, `production`, `test`) | +| Version | *(none)* | `app.version` | string | *(from package.json)* | Application version | +| ATT&CK Spec Version | *(none)* | `app.attackSpecVersion` | string | *(from package.json)* | ATT&CK specification version | + +**Example:** + +```bash +NODE_ENV=production +``` + +### Logging + +Logging configuration using Winston. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|-----------|----------------------|--------------------|--------|---------|---------------------| +| Log Level | `LOG_LEVEL` | `logging.logLevel` | string | `info` | Console log level | + +**Log Levels** (from least to most verbose): + +- `error` - Only errors +- `warn` - Warnings and errors +- `http` - HTTP requests, warnings, and errors +- `info` - General information (recommended for production) +- `verbose` - Detailed information +- `debug` - Debug messages (recommended for development) + +**Example:** + +```bash +LOG_LEVEL=debug +``` + +### Session + +Session management for user authentication. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|--------|----------------------|------------------|--------|--------------------------|-------------------------------------| +| Secret | `SESSION_SECRET` | `session.secret` | string | *(generated at startup)* | Secret used to sign session cookies | + +**Important Notes:** + +- If not set, a secret is generated randomly at startup +- Random secrets are regenerated on restart, forcing users to re-login +- Random secrets cannot be shared across multiple server instances +- **Production:** Always set `SESSION_SECRET` to a fixed, secure value + +**Generating a secure secret:** + +```bash +node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" +``` + +**Example:** + +```bash +SESSION_SECRET=your-secure-secret-here +``` + +### User Authentication + +Configuration for user authentication (how end-users log in). + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|-----------|----------------------|-----------------------|------|-------------|----------------------------------| +| Mechanism | `AUTHN_MECHANISM` | `userAuthn.mechanism` | enum | `anonymous` | Authentication mechanism to use | + +**Mechanism Options:** + +- `anonymous` - No authentication required (development only) +- `oidc` - OpenID Connect (recommended for production) + +#### OIDC Configuration + +Required when `mechanism` is set to `oidc`. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|-----------------|------------------------------|---------------------------------|--------|-------------------------|-----------------------------| +| Issuer URL | `AUTHN_OIDC_ISSUER_URL` | `userAuthn.oidc.issuerUrl` | string | *(empty)* | OIDC provider's issuer URL | +| Client ID | `AUTHN_OIDC_CLIENT_ID` | `userAuthn.oidc.clientId` | string | *(empty)* | OIDC client identifier | +| Client Secret | `AUTHN_OIDC_CLIENT_SECRET` | `userAuthn.oidc.clientSecret` | string | *(empty)* | OIDC client secret | +| Redirect Origin | `AUTHN_OIDC_REDIRECT_ORIGIN` | `userAuthn.oidc.redirectOrigin` | string | `http://localhost:3000` | Base URL for redirect URI | + +**Example:** + +```bash +AUTHN_MECHANISM=oidc +AUTHN_OIDC_ISSUER_URL=https://auth.example.com/realms/workbench +AUTHN_OIDC_CLIENT_ID=attack-workbench-rest-api +AUTHN_OIDC_CLIENT_SECRET=your-client-secret +AUTHN_OIDC_REDIRECT_ORIGIN=https://workbench.example.com +``` + +For detailed OIDC setup, see [Authentication Documentation](./authentication/README.md). + +### Service Authentication + +Configuration for service-to-service authentication (APIs, automation tools). + +The REST API supports three service authentication methods: + +1. **OIDC Client Credentials** - OAuth2 client credentials flow +2. **Challenge API Key** - Token exchange with challenge/response +3. **Basic API Key** - Simple API key authentication + +All methods support role-based access control with three service roles: + +- `read-only` - Read-only access to endpoints +- `collection-manager` - Read/write access for collection management +- `stix-export` - Access to STIX export endpoints + +#### OIDC Client Credentials + +Uses OAuth2 Client Credentials flow with JWT validation. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|----------|-------------------------------|----------------------------------------------|---------|-----------|------------------------------------------------| +| Enable | `SERVICE_ACCOUNT_OIDC_ENABLE` | `serviceAuthn.oidcClientCredentials.enable` | boolean | `false` | Enable OIDC client credentials authentication | +| JWKS URI | `JWKS_URI` | `serviceAuthn.oidcClientCredentials.jwksUri` | string | *(empty)* | JWKS endpoint for IdP public keys | +| Clients | *(JSON only)* | `serviceAuthn.oidcClientCredentials.clients` | array | `[]` | Array of authorized OIDC clients | + +**Clients Array Schema:** + +```json +{ + "clientId": "string", // OIDC client ID + "serviceRole": "enum" // Service role (read-only, collection-manager, stix-export) +} +``` + +**Example:** + +```bash +# .env +SERVICE_ACCOUNT_OIDC_ENABLE=true +JWKS_URI=https://auth.example.com/realms/workbench/protocol/openid-connect/certs +JSON_CONFIG_PATH=./config.json +``` + +```json +// config.json +{ + "serviceAuthn": { + "oidcClientCredentials": { + "enable": true, + "clients": [ + { + "clientId": "collection-manager-service", + "serviceRole": "collection-manager" + } + ] + } + } +} +``` + +See sample configurations: + +- [collection-manager-oidc-keycloak.json](../resources/sample-configurations/collection-manager-oidc-keycloak.json) +- [collection-manager-oidc-okta.json](../resources/sample-configurations/collection-manager-oidc-okta.json) + +#### Challenge API Key + +Token exchange authentication with challenge/response mechanism. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|----------------------|---------------------------------------------------|-----------------------------------------------|---------|--------------------------|---------------------------------------| +| Enable | `WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE` | `serviceAuthn.challengeApikey.enable` | boolean | `false` | Enable challenge API key authentication | +| Token Signing Secret | `WB_REST_TOKEN_SIGNING_SECRET` | `serviceAuthn.challengeApikey.secret` | string | *(generated at startup)* | Secret used to sign access tokens | +| Token Timeout | `WB_REST_TOKEN_TIMEOUT` | `serviceAuthn.challengeApikey.tokenTimeout` | integer | `300` | Access token lifetime in seconds | +| Service Accounts | *(JSON only)* | `serviceAuthn.challengeApikey.serviceAccounts`| array | `[]` | Array of service accounts | + +**Service Accounts Array Schema:** + +```json +{ + "name": "string", // Service account name + "apikey": "string", // Shared secret (API key) + "serviceRole": "enum" // Service role +} +``` + +**Example:** + +```bash +# .env +WB_REST_SERVICE_ACCOUNT_CHALLENGE_APIKEY_ENABLE=true +WB_REST_TOKEN_SIGNING_SECRET=your-secure-secret +WB_REST_TOKEN_TIMEOUT=600 +JSON_CONFIG_PATH=./config.json +``` + +```json +// config.json +{ + "serviceAuthn": { + "challengeApikey": { + "enable": true, + "serviceAccounts": [ + { + "name": "collection-manager", + "apikey": "your-secure-apikey", + "serviceRole": "collection-manager" + } + ] + } + } +} +``` + +See sample: [test-service-challenge-apikey.json](../resources/sample-configurations/test-service-challenge-apikey.json) + +#### Basic API Key + +Simple API key authentication (no challenge). + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|------------------|------------------------------------------------|--------------------------------------------|---------|---------|--------------------------------------| +| Enable | `WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE` | `serviceAuthn.basicApikey.enable` | boolean | `false` | Enable basic API key authentication | +| Service Accounts | *(JSON only)* | `serviceAuthn.basicApikey.serviceAccounts` | array | `[]` | Array of service accounts | + +**Service Accounts Array Schema:** + +```json +{ + "name": "string", // Service account name + "apikey": "string", // API key + "serviceRole": "enum" // Service role +} +``` + +**Example:** + +```bash +# .env +WB_REST_SERVICE_ACCOUNT_BASIC_APIKEY_ENABLE=true +JSON_CONFIG_PATH=./config.json +``` + +```json +// config.json +{ + "serviceAuthn": { + "basicApikey": { + "enable": true, + "serviceAccounts": [ + { + "name": "navigator", + "apikey": "your-navigator-apikey", + "serviceRole": "read-only" + } + ] + } + } +} +``` + +See sample: [navigator-basic-apikey.json](../resources/sample-configurations/navigator-basic-apikey.json) + +#### Multiple Service Authentication Methods + +You can enable multiple service authentication methods simultaneously: + +```json +{ + "serviceAuthn": { + "oidcClientCredentials": { + "enable": true, + "clients": [ + { + "clientId": "automated-collection-manager", + "serviceRole": "collection-manager" + } + ] + }, + "challengeApikey": { + "enable": true, + "serviceAccounts": [ + { + "name": "legacy-service", + "apikey": "legacy-apikey", + "serviceRole": "read-only" + } + ] + }, + "basicApikey": { + "enable": true, + "serviceAccounts": [ + { + "name": "navigator", + "apikey": "navigator-apikey", + "serviceRole": "read-only" + } + ] + } + } +} +``` + +See sample: [multiple-apikey-services.json](../resources/sample-configurations/multiple-apikey-services.json) + +### Scheduler + +Background job scheduler configuration. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|----------------|----------------------------|----------------------------------|---------|---------|--------------------------------------| +| Enable | `ENABLE_SCHEDULER` | `scheduler.enableScheduler` | boolean | `true` | Enable background job scheduler | +| Check Interval | `CHECK_WORKBENCH_INTERVAL` | `scheduler.checkWorkbenchInterval` | integer | `10` | Scheduler check interval in seconds | + +**Scheduler Functions:** + +- Checks for collection index updates +- Downloads collection bundles from remote URLs +- Processes subscription update policies + +**Example:** + +```bash +ENABLE_SCHEDULER=true +CHECK_WORKBENCH_INTERVAL=30 +``` + +### Collection Indexes + +Configuration for ATT&CK collection index subscriptions. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|------------------|----------------------|----------------------------------|---------|---------|-------------------------------------------| +| Default Interval | `DEFAULT_INTERVAL` | `collectionIndex.defaultInterval`| integer | `300` | Default update check interval in seconds | + +**Notes:** + +- Only applies to new collection indexes added after configuration change +- Does not affect existing collection indexes (they retain their configured interval) + +### Configuration Files + +Paths to additional configuration and data files. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|-----------------------------------|--------------------------------------|-----------------------------------------------------|--------|--------------------------------------------------|--------------------------------------------------| +| JSON Config Path | `JSON_CONFIG_PATH` | `configurationFiles.jsonConfigFile` | string | *(empty)* | Path to JSON configuration file | +| Allowed Values Path | `ALLOWED_VALUES_PATH` | `configurationFiles.allowedValues` | string | `./app/config/allowed-values.json` | Path to allowed values configuration | +| Static Marking Definitions Path | `WB_REST_STATIC_MARKING_DEFS_PATH` | `configurationFiles.staticMarkingDefinitionsPath` | string | `./app/lib/default-static-marking-definitions/` | Directory containing static marking definitions | + +**Allowed Values:** + +The allowed values file defines valid enum values for STIX object properties (platforms, permissions, etc.). +See `app/config/allowed-values.json` for the schema. + +**Static Marking Definitions:** + +Directory containing JSON files with STIX marking definitions that are automatically loaded into the system on startup. + +### ATT&CK Specific + +ATT&CK-specific configuration values. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|--------------------------|----------------------|------------------------|--------|------------|-----------------------------------------------------------------| +| Attack Source Names | *(JSON only)* | `attackSourceNames` | array | See below | Valid `source_name` values in ATT&CK `external_references` | +| Domain to Kill Chain Map | *(JSON only)* | `domainToKillChainMap` | object | See below | Maps domain names to kill chain phase names | + +**Default Attack Source Names:** + +```json +["mitre-attack", "mitre-mobile-attack", "mobile-attack", "mitre-ics-attack"] +``` + +**Default Domain to Kill Chain Map:** + +```json +{ + "enterprise-attack": "mitre-attack", + "mobile-attack": "mitre-mobile-attack", + "ics-attack": "mitre-ics-attack" +} +``` + +--- + +## Additional Resources + +- [Authentication Documentation](./authentication/README.md) +- [Sample Configurations](../resources/sample-configurations/) +- [Template Environment File](../template.env) diff --git a/docs/legacy/authentication.md b/docs/legacy/authentication.md index 95953d60..ee9214e2 100644 --- a/docs/legacy/authentication.md +++ b/docs/legacy/authentication.md @@ -89,6 +89,28 @@ Logs the user out of the REST API. OIDC authentication is intended for use in an organizational setting and can be tied into the organization's single-sign on configuration. +##### Configuring OIDC for Multiple Environments + +When deploying multiple instances of the REST API (e.g., local development, staging, and production), each instance requires its own `AUTHN_OIDC_REDIRECT_ORIGIN` configured to match the URL where that instance is accessible. + +For example: + +- **Local instance**: `AUTHN_OIDC_REDIRECT_ORIGIN=http://localhost:3000` +- **Production instance**: `AUTHN_OIDC_REDIRECT_ORIGIN=https://workbench.example.com` + +All instances can share the same OIDC Client ID and Client Secret, but you must configure **all redirect URIs** in your OIDC Identity Provider: + +- `http://localhost:3000/api/authn/oidc/callback` +- `https://workbench.example.com/api/authn/oidc/callback` + +**Example with Authentik:** + +1. In Authentik, navigate to **Applications** → **Providers** → your provider +2. Edit the **Redirect URIs/Origins** field +3. Add each environment's callback URI on a separate line + +This allows the same OIDC provider configuration to work with multiple deployed instances. + ##### Log In ``` diff --git a/docs/legacy/user-management.md b/docs/legacy/user-management.md index c3337f20..6d0df8a6 100644 --- a/docs/legacy/user-management.md +++ b/docs/legacy/user-management.md @@ -71,9 +71,9 @@ As shown in the table, the default role for users who aren't registered and acti These endpoints are disabled if the app is configured to use the anonymous authentication mechanism. The STIX ID of the corresponding identity object is used by the user management endpoints as the unique identifier for a user. -##### Get Users +### Get Users -``` +```http GET /api/user-accounts ``` @@ -81,25 +81,25 @@ Retrieves a list of user account documents (i.e., registered users). Query string parameters for searching are TBD. -###### Authorization +#### Authorization This endpoint will only be available to users with the `admin` role. -##### Get User +### Get User -``` +```http GET /api/user-accounts/:id ``` Retrieve a user account document by its id. -###### Authorization +#### Authorization This endpoint will only be available to users with the `admin` role or for a logged in user with the matching user account `id`. -##### Register User +### Register User -``` +```http POST /api/user-accounts/register ``` @@ -108,24 +108,83 @@ This results in a registered user. The user document will have the `email` and `username` properties set based on the log in data. `status` will be set to pending and `role` will be set to null. -###### Authorization +#### Authorization This endpoint will only be available for a logged in user who is in the process of registering. -##### Update User +### Update User -``` +```http PUT /api/user-accounts/:id ``` Update an existing user document in the database. -###### Authorization +#### Authorization This endpoint will only be available to users with the `admin` role. +## Creating User Accounts + +When using OIDC authentication, users who successfully authenticate through the identity provider will not have any permissions in the Workbench until a user account is created for them. + +### Process + +1. **User authenticates for the first time**: + - The user logs in through the OIDC provider + - They will see a "User not registered" message or have no permissions (effective role: `none`) + +2. **Administrator creates user account**: + - An existing admin creates the account via the REST API or frontend + - The email must match the email claim from the OIDC provider + +3. **User logs in again**: + - The user will now have the assigned role and permissions + +### Creating Accounts via REST API + +Use the `POST /api/user-accounts` endpoint: + +```bash +curl -X POST http://localhost:3000/api/user-accounts \ + -H "Content-Type: application/json" \ + -d '{ + "email": "user@example.com", + "username": "user@example.com", + "displayName": "User Display Name", + "status": "active", + "role": "editor" + }' +``` + +**Required fields:** + +- `email` (string): Must match the email claim from the OIDC provider +- `username` (string): Typically the same as email or the `preferred_username` claim +- `displayName` (string): User's full name for display +- `status` (string): Set to `active` for immediate access +- `role` (string): One of `admin`, `editor`, `visitor`, or `team_lead` + ## Initial User Configuration -Unless the app is configured to use the anonymous authentication mechanism, -it will be necessary to have a way to provision an initial admin user that can then be used to create the other users. -The design for provisioning the initial admin user is TBD. +For OIDC-based authentication, you need at least one admin user to manage other users. + +### Bootstrapping the First Admin + +#### Option 1: Direct Database Insert (for initial setup only) + +If you have direct access to MongoDB, you can create the first admin user manually: + +1. Have the user authenticate once through OIDC (they'll have no permissions) +2. Get their email from the OIDC provider +3. Insert a user account document directly into the database with admin role +4. User logs in again with admin permissions + +#### Option 2: Temporary Anonymous Access + +1. Temporarily switch to `AUTHN_MECHANISM=anonymous` in `.env` +2. Restart the REST API +3. Access the system with admin privileges +4. Create OIDC user accounts through the API or frontend +5. Switch back to `AUTHN_MECHANISM=oidc` +6. Restart the REST API From 01cbeff26a11ee780ff8d40827831b6cfccd607a Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 12:25:29 -0600 Subject: [PATCH 063/370] style: markdownlint fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 086b16e9..e37b1a3c 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,10 @@ Copyright 2020-2025 MITRE Engenuity. Approved for public release. Document numbe Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This project makes use of ATT&CK® -[ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/) \ No newline at end of file +[ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/) From 96700206be34741284aed4cea09366474e4fc854 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 12:25:59 -0600 Subject: [PATCH 064/370] docs: add link to new configuration page --- USAGE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/USAGE.md b/USAGE.md index a3db17c4..e7fe83c7 100644 --- a/USAGE.md +++ b/USAGE.md @@ -49,7 +49,8 @@ The recommended deployment method is using Docker. The REST API is published as #### Using Docker Compose (Recommended) -The simplest way to deploy the entire ATT&CK Workbench application is using Docker Compose. Instructions are available in the [Workbench Deployment Guide](https://github.com/mitre-attack/attack-workbench-deployment). +The simplest way to deploy the entire ATT&CK Workbench application is using Docker Compose. +Instructions are available in the [Workbench Deployment Guide](https://github.com/mitre-attack/attack-workbench-deployment). #### Standalone Docker Deployment @@ -90,6 +91,8 @@ docker run -p 3000:3000 -d \ ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api:latest ``` +More infomation about configuration options is in the [configuration file documentation](./docs/configuration.md). + ### Manual Installation #### Requirements From 02f701f1b13ee4c909d9d9edb7832e1e57b4f0f8 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 12:52:15 -0600 Subject: [PATCH 065/370] docs: add MONGOSTORE_CRYPTO_SECRET to documentation --- docs/configuration.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d65345cd..5b04cf49 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -243,9 +243,10 @@ LOG_LEVEL=debug Session management for user authentication. -| Option | Environment Variable | JSON Path | Type | Default | Description | -|--------|----------------------|------------------|--------|--------------------------|-------------------------------------| -| Secret | `SESSION_SECRET` | `session.secret` | string | *(generated at startup)* | Secret used to sign session cookies | +| Option | Environment Variable | JSON Path | Type | Default | Description | +|----------------------|--------------------------------|----------------------------------|--------|--------------------------|-------------------------------------------| +| Secret | `SESSION_SECRET` | `session.secret` | string | *(generated at startup)* | Secret used to sign session cookies | +| Mongo Session Secret | `MONGOSTORE_CRYPTO_SECRET` | `session.mongoStoreCryptoSecret` | string | *(generated at startup)* | Secret to encrypt session data in MongoDB | **Important Notes:** @@ -264,6 +265,7 @@ node -e "console.log(require('crypto').randomBytes(48).toString('base64'))" ```bash SESSION_SECRET=your-secure-secret-here +MONGOSTORE_CRYPTO_SECRET=your-secure-secret-here ``` ### User Authentication From e8787b6cead45427d68da5ab0305ce8883c329ca Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Mon, 10 Nov 2025 13:15:37 -0600 Subject: [PATCH 066/370] docs: consolidate docs about configuration --- USAGE.md | 92 ++--------------------------------- docs/authentication/README.md | 8 +++ 2 files changed, 12 insertions(+), 88 deletions(-) diff --git a/USAGE.md b/USAGE.md index e7fe83c7..bfb8c3b8 100644 --- a/USAGE.md +++ b/USAGE.md @@ -15,12 +15,7 @@ This guide provides comprehensive instructions for installing, configuring, and - [Requirements](#requirements) - [Installation Steps](#installation-steps) - [Configuration](#configuration) - - [Environment Variables](#environment-variables) - - [Configuration File](#configuration-file) - [Authentication](#authentication) - - [Authentication Mechanisms](#authentication-mechanisms) - - [OpenID Connect (OIDC) Configuration](#openid-connect-oidc-configuration) - - [Service Authentication](#service-authentication) - [User Management](#user-management) - [User Roles and Permissions](#user-roles-and-permissions) - [User Account Status](#user-account-status) @@ -125,92 +120,13 @@ More infomation about configuration options is in the [configuration file docume ## Configuration -The REST API can be configured using environment variables, a configuration file, or a combination of both. Configuration file values take precedence over environment variables. - -### Environment Variables - -| Variable | Required | Default | Description | -|--------------------------------------|----------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| -| **PORT** | No | `3000` | Port the HTTP server should listen on | -| **CORS_ALLOWED_ORIGINS** | No | `*` | Configures CORS policy. Accepts a comma-separated list of allowed domains. (`*` allows all domains; `disable` disables CORS entirely.) | -| **NODE_ENV** | No | `development` | Environment that the app is running in | -| **DATABASE_URL** | Yes | none | URL of the MongoDB server | -| **AUTHN_MECHANISM** | No | `anonymous` | Mechanism to use for authenticating users | -| **DEFAULT_INTERVAL** | No | `300` | How often collection indexes should check for updates (in seconds) | -| **JSON_CONFIG_PATH** | No | `` | Location of a JSON file containing configuration values | -| **LOG_LEVEL** | No | `info` | Level of messages to be written to the log (error, warn, http, info, verbose, debug) | -| **WB_REST_STATIC_MARKING_DEFS_PATH** | No | `./app/lib/default-static-marking-definitions/` | Path to a directory containing static marking definitions | - -A typical value for DATABASE_URL when running locally is `mongodb://localhost/attack-workspace`. - -### Configuration File - -If the `JSON_CONFIG_PATH` environment variable is set, the app will read configuration settings from a JSON file at that location. - -| Property | Type | Corresponding Environment Variable | -|-------------------------------------|--------------|------------------------------------| -| **server.port** | int | PORT | -| **server.corsAllowedOrigins** | string/array | CORS_ALLOWED_ORIGINS | -| **app.env** | string | NODE_ENV | -| **database.url** | string | DATABASE_URL | -| **collectionIndex.defaultInterval** | int | DEFAULT_INTERVAL | -| **logging.logLevel** | string | LOG_LEVEL | - -Example configuration file: - -```json -{ - "server": { - "port": 4000, - "corsAllowedOrigins": ["https://example.com", "https://workbench.example.com"] - }, - "database": { - "url": "mongodb://localhost/attack-workspace" - }, - "logging": { - "logLevel": "debug" - } -} -``` +The REST API can be configured using environment variables, a configuration file, or a combination of both. +Read all about it in the [configuration docs](./docs/configuration.md). ## Authentication -The REST API supports different authentication mechanisms for both user and service authentication. - -### Authentication Mechanisms - -The application supports these user authentication mechanisms: - -- **Anonymous**: Default mechanism with no actual authentication (primarily for local development) -- **OpenID Connect (OIDC)**: Integration with organizational identity providers - -### OpenID Connect (OIDC) Configuration - -To enable OIDC authentication: - -1. **Register with your OIDC Identity Provider** with these details: - - Authentication flow: Authorization Code Flow - - Required claims: `email` (required), `preferred_username` (optional), `name` (optional) - - Grant Types: Client Credentials, Authorization Code, and Refresh Token - - Redirect URL: `/api/authn/oidc/callback` - -2. **Configure the REST API** with these environment variables: - -| Environment Variable | Required | Description | Configuration Property | -|--------------------------------|----------|---------------------------------------|-------------------------------| -| **AUTHN_MECHANISM** | Yes | Must be set to `oidc` | userAuthn.mechanism | -| **AUTHN_OIDC_CLIENT_ID** | Yes | Client ID from your OIDC provider | userAuthn.oidc.clientId | -| **AUTHN_OIDC_CLIENT_SECRET** | Yes | Client secret from your OIDC provider | userAuthn.oidc.clientSecret | -| **AUTHN_OIDC_ISSUER_URL** | Yes | Issuer URL for the Identity Server | userAuthn.oidc.issuerUrl | -| **AUTHN_OIDC_REDIRECT_ORIGIN** | Yes | URL for the Workbench host | userAuthn.oidc.redirectOrigin | - -### Service Authentication - -For service-to-service communication, the REST API supports three methods: - -1. **API Key Challenge Authentication**: Services obtain a JWT using a challenge-response protocol -2. **API Key Basic Authentication**: Services authenticate using HTTP Basic Authentication -3. **OIDC Client Credentials Flow**: Services obtain a JWT from an OIDC provider +The REST API has several authentication options. +Read all about them in the [authentication docs](./docs/authentication/README.md). ## User Management diff --git a/docs/authentication/README.md b/docs/authentication/README.md index 3b0b7e78..835abc06 100644 --- a/docs/authentication/README.md +++ b/docs/authentication/README.md @@ -66,6 +66,14 @@ In your OIDC provider, configure **all** redirect URIs: - `http://localhost:3000/api/authn/oidc/callback` - `https://workbench.example.com/api/authn/oidc/callback` +## Service Authentication + +For service-to-service communication, the REST API supports three methods: + +1. **API Key Challenge Authentication**: Services obtain a JWT using a challenge-response protocol +2. **API Key Basic Authentication**: Services authenticate using HTTP Basic Authentication +3. **OIDC Client Credentials Flow**: Services obtain a JWT from an OIDC provider + ## User Management After configuring OIDC, users who log in will be authenticated but will not have any permissions until you create a user account for them in the Workbench. From 82f61e59fd62dd4a44a17ed29959dab75003cadb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:08:59 -0500 Subject: [PATCH 067/370] feat(techniques-controller): add new parentTechniqueId query param - Preparation for migration sub-technique conversion workflow to backend - Also necessary for setting ATT&CK IDs --- app/api/definitions/paths/techniques-paths.yml | 12 ++++++++++++ app/controllers/techniques-controller.js | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index effaf3fb..bbae9da8 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -122,8 +122,20 @@ paths: This endpoint creates a new technique in the workspace. If the `stix.id` property is set, it creates a new version of an existing technique. If the `stix.id` property is not set, it creates a new technique, generating a STIX id for it. + + For subtechniques (when `x_mitre_is_subtechnique` is true), you must provide the `parentTechniqueId` query parameter + to specify the parent technique's ATT&CK ID. tags: - 'Techniques' + parameters: + - name: parentTechniqueId + in: query + description: | + Parent technique ATT&CK ID (e.g., T1234). Required when creating subtechniques (`x_mitre_is_subtechnique: true`). + schema: + type: string + pattern: '^T\d{4}$' + example: 'T1234' requestBody: required: true content: diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index c0133537..f4b154d7 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -95,6 +95,7 @@ exports.create = async function (req, res) { const options = { import: false, userAccountId: req.user?.userAccountId, + parentTechniqueId: req.query.parentTechniqueId, // NOTE this is new! }; // Create the technique @@ -134,8 +135,13 @@ exports.updateFull = async function (req, res) { return res.status(200).send(technique); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update technique. Server error.'); + if (err.name === 'ImmutablePropertyError') { + logger.warn(`Attempt to modify immutable property: ${err.message}`); + return res.status(400).send(err.message); + } else { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to update technique. Server error.'); + } } }; From b89273e859a7cf61bfbc6d8df55812dc74985507 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:09:41 -0500 Subject: [PATCH 068/370] feat(workspace-model): add support for tracking embedded rels on SDO docs --- app/models/subschemas/workspace.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/subschemas/workspace.js b/app/models/subschemas/workspace.js index 7bb99572..ad8ef3ca 100644 --- a/app/models/subschemas/workspace.js +++ b/app/models/subschemas/workspace.js @@ -8,6 +8,19 @@ const collectionVersion = { }; const collectionVersionSchema = new mongoose.Schema(collectionVersion, { _id: false }); +const embedddedRelationship = { + stix_id: { type: String, required: true }, + attack_id: String, + direction: { + type: String, + // inbound: The embedded relationship points TO this document (I am referenced) + // outbound: The embedded relationship points FROM this document (I reference another) + enum: ['inbound', 'outbound'], + required: true, + }, +}; +const embeddedRelationshipSchema = new mongoose.Schema(embedddedRelationship, { _id: false }); + /** * Workspace property definition for most object types */ @@ -15,12 +28,13 @@ module.exports.common = { workflow: { state: { type: String, - enum: ['work-in-progress', 'awaiting-review', 'reviewed', 'static'], + enum: ['work-in-progress', 'awaiting-review', 'reviewed', 'static', 'draft'], }, created_by_user_account: String, }, attack_id: String, collections: [collectionVersionSchema], + embedded_relationships: { type: [embeddedRelationshipSchema], required: true }, }; // x-mitre-collection workspace structure From 37260e93bca9bd87f5cc44fcefba1099be179da7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:10:21 -0500 Subject: [PATCH 069/370] feat(exceptions): set this.name as constructor name to clarify nature of error --- app/exceptions/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/exceptions/index.js b/app/exceptions/index.js index f46351e5..98e5046d 100644 --- a/app/exceptions/index.js +++ b/app/exceptions/index.js @@ -4,6 +4,9 @@ class CustomError extends Error { constructor(message, options = {}) { super(message); + // Set the error name to the class name + this.name = this.constructor.name; + // Apply options (if defined) to the error object for (const key in options) { if (Object.prototype.hasOwnProperty.call(options, key)) { @@ -169,6 +172,18 @@ class InvalidTypeError extends CustomError { } } +class ImmutablePropertyError extends CustomError { + constructor(propertyName, options) { + super(`Cannot modify immutable property: ${propertyName}`, options); + } +} + +class InvalidPostOperationError extends CustomError { + constructor(options) { + super('Cannot set the following keys:', options); + } +} + module.exports = { //** General errors */ NotImplementedError, @@ -179,6 +194,8 @@ module.exports = { BadlyFormattedParameterError, InvalidQueryStringParameterError, CannotUpdateStaticObjectError, + ImmutablePropertyError, + InvalidPostOperationError, //** Database-related errors */ DuplicateIdError, From eaa8000a714065fb8145094839a1ffe369335609 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:14:15 -0500 Subject: [PATCH 070/370] feat(repository): add new retrieveLatestByStixId method - retrieveLatestByStixId renamed to retrieveLatestByStixIdLean - retrieveLatestByStix created without lean call --- app/repository/_abstract.repository.js | 2 +- app/repository/_base.repository.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/repository/_abstract.repository.js b/app/repository/_abstract.repository.js index a6eafafe..89d413eb 100644 --- a/app/repository/_abstract.repository.js +++ b/app/repository/_abstract.repository.js @@ -37,7 +37,7 @@ class AbstractRepository { * @param {*} stixId The unique identifier for the document. * @returns {Object} The retrieved document. */ - async retrieveLatestByStixId(stixId) { + async retrieveLatestByStixIdLean(stixId) { throw new NotImplementedError(this.constructor.name, 'retrieveLatestByStixId'); } diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index eac445a1..7ce31d0b 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -171,6 +171,14 @@ class BaseRepository extends AbstractRepository { } } + async retrieveLatestByStixId(stixId) { + try { + return await this.model.findOne({ 'stix.id': stixId }).sort('-stix.modified').exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + async retrieveAllById(stixId) { try { return await this.model.find({ 'stix.id': stixId }).sort('-stix.modified').lean().exec(); @@ -179,7 +187,7 @@ class BaseRepository extends AbstractRepository { } } - async retrieveLatestByStixId(stixId) { + async retrieveLatestByStixIdLean(stixId) { try { return await this.model.findOne({ 'stix.id': stixId }).sort('-stix.modified').lean().exec(); } catch (err) { From 13382010edf1d9d2f259d9109755c6a17b01750a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:14:59 -0500 Subject: [PATCH 071/370] feat(analytics-controller): handle immutable errors thrown by analytics service --- app/controllers/analytics-controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/analytics-controller.js b/app/controllers/analytics-controller.js index feb72ad7..fde0e9b0 100644 --- a/app/controllers/analytics-controller.js +++ b/app/controllers/analytics-controller.js @@ -6,6 +6,7 @@ const { DuplicateIdError, BadlyFormattedParameterError, InvalidQueryStringParameterError, + ImmutablePropertyError, } = require('../exceptions'); exports.retrieveAll = async function (req, res) { @@ -105,6 +106,9 @@ exports.create = async function (req, res) { logger.debug('Success: Created analytic with id ' + analytic.stix.id); return res.status(201).send(analytic); } catch (err) { + if (err instanceof ImmutablePropertyError) { + return res.status(400).send(err.message); + } if (err instanceof DuplicateIdError) { logger.warn('Duplicate stix.id and stix.modified'); return res From 638c79833e2b4f5a40ffef33d33cbe80d6e1629d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:16:04 -0500 Subject: [PATCH 072/370] feat(base-service): auto generate attack id and ext refs in base create method --- app/services/_base.service.js | 135 ++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 23 deletions(-) diff --git a/app/services/_base.service.js b/app/services/_base.service.js index 5a2c9987..54e5addd 100644 --- a/app/services/_base.service.js +++ b/app/services/_base.service.js @@ -4,6 +4,11 @@ const uuid = require('uuid'); const logger = require('../lib/logger'); const config = require('../config/config'); const attackIdGenerator = require('../lib/attack-id-generator'); +const { + createAttackExternalReference, + findAttackExternalReference, + validateAttackExternalReference, +} = require('../lib/external-reference-builder'); const { DatabaseError, IdentityServiceError, @@ -11,6 +16,8 @@ const { InvalidQueryStringParameterError, InvalidTypeError, OrganizationIdentityNotSetError, + ImmutablePropertyError, + InvalidPostOperationError, } = require('../exceptions'); const AbstractService = require('./_abstract.service'); @@ -114,7 +121,7 @@ class BaseService extends AbstractService { if (!attackObject.created_by_identity) { try { - const identityObject = await identitiesRepository.retrieveLatestByStixId( + const identityObject = await identitiesRepository.retrieveLatestByStixIdLean( attackObject.stix.created_by_ref, ); attackObject.created_by_identity = identityObject; @@ -136,7 +143,7 @@ class BaseService extends AbstractService { if (!attackObject.modified_by_identity) { try { - const identityObject = await identitiesRepository.retrieveLatestByStixId( + const identityObject = await identitiesRepository.retrieveLatestByStixIdLean( attackObject.stix.x_mitre_modified_by_ref, ); attackObject.modified_by_identity = identityObject; @@ -315,42 +322,89 @@ class BaseService extends AbstractService { options = options || {}; if (!options.import) { + const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( + data.stix, + ); + const attackIdInWorkspace = data.workspace.attack_id; + const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; + const parentTechniqueId = options?.parentTechniqueId; + + // Throw (reject request) if user attempts to manually define the ATT&CK ID -- this field is controlled exclusively by the backend + if (attackIdInExternalReferences) { + logger.warn( + 'Immutable property: user attempted to set backend-controlled property, external_references.0.external_id', + ); + throw new ImmutablePropertyError('external_references.0.external_id'); + } else if (attackIdInWorkspace) { + logger.warn( + 'Immutable property: user attempted to set backend-controlled property, workspace.attack_id', + ); + throw new ImmutablePropertyError('workspace.attack_id'); + } + + // Generate ATT&CK ID if (attackIdGenerator.requiresAttackId(this.type)) { - // Generate or validate ATT&CK ID - const hasValidId = await attackIdGenerator.hasValidAttackId( - data, + // Validate subtechnique requirements + if (isSubtechnique && !parentTechniqueId) { + const errorMessage = + 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).'; + logger.error(errorMessage); + throw new InvalidPostOperationError(errorMessage); + } + if (!isSubtechnique && parentTechniqueId) { + const errorMessage = + 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).'; + logger.error(errorMessage); + throw new InvalidPostOperationError(errorMessage); + } + + // Set the ATT&CK ID! + const attackId = await attackIdGenerator.generateAttackId( this.type, this.repository, + isSubtechnique, + parentTechniqueId, ); - if (!hasValidId) { - // No ID present, generate one - // Note: Only supports regular ID generation, not subtechniques - // For subtechniques, the client must provide the ATT&CK ID explicitly - const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; - if (isSubtechnique) { - throw new Error( - 'Subtechniques require an explicit ATT&CK ID in workspace.attack_id. Use the /api/attack-objects/attack-id/next endpoint with parentRef to preview the ID.', - ); - } - - const attackId = await attackIdGenerator.generateAttackId( - this.type, - this.repository, - false, - null, + data.workspace = data.workspace || {}; + data.workspace.attack_id = attackId; + logger.debug('Generated and set ATT&CK ID:', attackId); + } + + // Handle ATT&CK external reference for CREATE operations + if (data.stix?.external_references) { + // On create, clients MUST NOT provide ATT&CK external references - the backend controls this + // Throw (reject request) if user attempts to manually set the MITRE citation in the external_references array + const mitreAttackRefInExternalReferences = + attackIdGenerator.extractAttackIdFromExternalReferences(data.stix); + if (mitreAttackRefInExternalReferences) { + logger.error( + 'User manually attempted to set the MITRE ATT&CK citation at external_references.0', ); - data.workspace = data.workspace || {}; - data.workspace.attack_id = attackId; + throw new ImmutablePropertyError('external_references.0', { + input: mitreAttackRefInExternalReferences, + }); } + } else { + data.stix.external_references = []; + } + + // Generate and add the ATT&CK external reference + const attackRef = createAttackExternalReference(data); + if (attackRef) { + data.stix.external_references.unshift(attackRef); } + console.debug('Generated and set the MITRE ATT&CK external reference:', attackRef); + // Set the ATT&CK Spec Version data.stix.x_mitre_attack_spec_version = data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; + console.debug('Set the ATT&CK specification version:', data.stix.x_mitre_attack_spec_version); // Record the user account that created the object if (options.userAccountId) { data.workspace.workflow.created_by_user_account = options.userAccountId; + console.debug('Recorded the user account that created the object:', options.userAccountId); } // Set the default marking definitions @@ -398,6 +452,41 @@ class BaseService extends AbstractService { return null; } + // Handle ATT&CK external reference for UPDATE operations + // On update, clients CAN provide ATT&CK external references, but they must match the existing data + // and cannot be modified + if (data.workspace?.attack_id) { + // Validate that workspace.attack_id hasn't changed + if (data.workspace.attack_id !== document.workspace.attack_id) { + throw new ImmutablePropertyError('workspace.attack_id', { + details: `Expected '${document.workspace.attack_id}' but received '${data.workspace.attack_id}'`, + }); + } + } + + if (data.stix?.external_references && attackIdGenerator.requiresAttackId(this.type)) { + const clientAttackRef = findAttackExternalReference(data.stix.external_references); + const expectedAttackRef = createAttackExternalReference(document); + + if (clientAttackRef && expectedAttackRef) { + // Validate that the client-provided ATT&CK reference matches expectations + const validation = validateAttackExternalReference(clientAttackRef, expectedAttackRef); + if (!validation.isValid) { + throw new ImmutablePropertyError('stix.external_references[0] (ATT&CK reference)', { + details: validation.error, + }); + } + + // If client didn't provide URL but should have, add it + if (expectedAttackRef.url && !clientAttackRef.url) { + clientAttackRef.url = expectedAttackRef.url; + } + } else if (!clientAttackRef && expectedAttackRef) { + // Client didn't provide ATT&CK reference - add it + data.stix.external_references.unshift(expectedAttackRef); + } + } + const newDocument = await this.repository.updateAndSave(document, data); if (newDocument === document) { From abec8e2b06c13a38e2ebc9c43f8005a825480737 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:24:49 -0500 Subject: [PATCH 073/370] feat(workspace): add support for embedded_relationships in OpenAPI schemas This will probably be revoked in the future. Allowing clients to manually set embedded relationships is dangerous. --- app/api/definitions/components/workspace.yml | 30 +++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/app/api/definitions/components/workspace.yml b/app/api/definitions/components/workspace.yml index afe3a51a..52a8a080 100644 --- a/app/api/definitions/components/workspace.yml +++ b/app/api/definitions/components/workspace.yml @@ -9,14 +9,16 @@ components: properties: state: type: string - enum: ['work-in-progress', 'awaiting-review', 'reviewed', 'static'] - attackId: - type: string - example: 'T9999' + enum: ['work-in-progress', 'awaiting-review', 'reviewed', 'static', 'draft'] collections: type: array items: $ref: '#/components/schemas/collection_reference' + embedded_relationships: + type: array + items: + $ref: '#/components/schemas/embedded_relationship' + description: 'References to parent objects in embedded relationships (e.g., analytics referencing detection strategies)' collection_reference: type: object @@ -29,3 +31,23 @@ components: required: - collection_ref - collection_modified + + embedded_relationship: + type: object + properties: + stix_id: + type: string + description: 'STIX ID of the related object' + example: 'x-mitre-detection-strategy--12345678-1234-1234-1234-123456789abc' + attack_id: + type: string + description: 'ATT&CK ID of the related object (optional)' + example: 'DET0001' + direction: + type: string + enum: ['inbound', 'outbound'] + description: 'Specifies whether this relationship is pointing to me or I to it' + example: 'inbound' + required: + - stix_id + - direction From bf554a14d754630f14f253d56c07a4fc9e19a55d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:25:24 -0500 Subject: [PATCH 074/370] fix(attack-id-generator): resolve small bug in requiresAttackId function --- app/lib/attack-id-generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js index f87e4899..37c8bb47 100644 --- a/app/lib/attack-id-generator.js +++ b/app/lib/attack-id-generator.js @@ -14,7 +14,7 @@ const logger = require('./logger'); * @returns {boolean} True if the object type requires an ATT&CK ID */ function requiresAttackId(objectType) { - return objectType in Object.keys(stixTypeToAttackIdMapping); + return objectType in stixTypeToAttackIdMapping; } exports.requiresAttackId = requiresAttackId; From e5c8e81de047537c8a4a5ccab8f072282879359b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:25:55 -0500 Subject: [PATCH 075/370] feat(external-reference-builder): add new lib/utility module for handling external refs --- app/lib/external-reference-builder.js | 208 ++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 app/lib/external-reference-builder.js diff --git a/app/lib/external-reference-builder.js b/app/lib/external-reference-builder.js new file mode 100644 index 00000000..f59798a0 --- /dev/null +++ b/app/lib/external-reference-builder.js @@ -0,0 +1,208 @@ +'use strict'; + +/** + * Map STIX types to their corresponding ATT&CK website URL paths + */ +const STIX_TYPE_TO_URL_PATH = { + 'attack-pattern': 'techniques', + 'intrusion-set': 'groups', + malware: 'software', + tool: 'software', + 'course-of-action': 'mitigations', + campaign: 'campaigns', + 'x-mitre-data-source': 'datasources', + 'x-mitre-data-component': 'datacomponents', + 'x-mitre-detection-strategy': 'detection-strategies', + 'x-mitre-analytic': 'analytics', + 'x-mitre-asset': 'assets', + 'x-mitre-tactic': 'tactics', +}; + +/** + * Build the ATT&CK external reference object for a given ATT&CK ID and STIX type + * @param {string} attackId - The ATT&CK ID (e.g., T0001, G0042, T1234.001) + * @param {string} stixType - The STIX type (e.g., attack-pattern, intrusion-set) + * @param {object} options - Optional parameters + * @param {boolean} options.isSubtechnique - Whether this is a subtechnique + * @param {string} options.parentDetectionStrategyId - For analytics, the parent detection strategy ATT&CK ID + * @returns {object} External reference object with source_name, external_id, and url + */ +function buildAttackExternalReference(attackId, stixType, options = {}) { + if (!attackId || !stixType) { + return null; + } + + // Special case: Analytics with a parent detection strategy get a custom URL + if (stixType === 'x-mitre-analytic') { + if (options.parentDetectionStrategyId) { + return { + source_name: 'mitre-attack', + external_id: attackId, + url: `https://attack.mitre.org/detectionstrategies/${options.parentDetectionStrategyId}#${attackId}`, + }; + } else { + // No parent detection strategy, return reference without URL + return { + source_name: 'mitre-attack', + external_id: attackId, + }; + } + } + + const urlPath = STIX_TYPE_TO_URL_PATH[stixType]; + if (!urlPath) { + // Type doesn't have a URL mapping, return reference without URL + return { + source_name: 'mitre-attack', + external_id: attackId, + }; + } + + let url; + const isSubtechnique = options.isSubtechnique || attackId.includes('.'); + if (stixType === 'attack-pattern' && isSubtechnique) { + // Subtechniques use format: /techniques/T1234/001 + const [parentId, subId] = attackId.split('.'); + url = `https://attack.mitre.org/${urlPath}/${parentId}/${subId}`; + } else { + url = `https://attack.mitre.org/${urlPath}/${attackId}`; + } + + return { + source_name: 'mitre-attack', + external_id: attackId, + url, + }; +} + +/** + * Extract parent detection strategy ATT&CK ID from workspace embedded relationships + * @param {object} data - The data object containing workspace + * @returns {string|null} The detection strategy ATT&CK ID or null + */ +function extractParentDetectionStrategyId(data) { + // Check if this is an analytic + if (data.stix?.type !== 'x-mitre-analytic') { + return null; + } + + // Look for parent detection strategy in embedded relationships + // Analytics are referenced by detection strategies via x_mitre_analytic_refs + // So we look for inbound relationships from detection strategies + const embeddedRelationships = data.workspace?.embedded_relationships; + if ( + embeddedRelationships && + Array.isArray(embeddedRelationships) && + embeddedRelationships.length > 0 + ) { + // Find any inbound relationship from a detection strategy + const parentDetectionStrategy = embeddedRelationships.find( + (rel) => + rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--'), + ); + + if (parentDetectionStrategy) { + return parentDetectionStrategy.attack_id || null; + } + } + + return null; +} + +/** + * Create an ATT&CK external reference for the given data + * @param {object} data - The data object containing stix and workspace + * @returns {object|null} The ATT&CK external reference object, or null if not applicable + */ +function createAttackExternalReference(data) { + const attackId = data.workspace?.attack_id; + const stixType = data.stix?.type; + + if (!attackId || !stixType) { + return null; + } + + // Prepare options for URL building + const options = {}; + if (stixType === 'attack-pattern') { + options.isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; + } + if (stixType === 'x-mitre-analytic') { + options.parentDetectionStrategyId = extractParentDetectionStrategyId(data); + } + + return buildAttackExternalReference(attackId, stixType, options); +} + +/** + * Check if an external reference is an ATT&CK reference + * @param {object} ref - The external reference object + * @returns {boolean} True if this is an ATT&CK reference + */ +function isAttackExternalReference(ref) { + return ref && ref.source_name === 'mitre-attack'; +} + +/** + * Find the ATT&CK external reference in an array + * @param {Array} externalReferences - Array of external reference objects + * @returns {object|null} The ATT&CK external reference, or null if not found + */ +function findAttackExternalReference(externalReferences) { + if (!externalReferences || !Array.isArray(externalReferences)) { + return null; + } + return externalReferences.find(isAttackExternalReference) || null; +} + +/** + * Remove all ATT&CK external references from an array + * @param {Array} externalReferences - Array of external reference objects + * @returns {Array} New array with ATT&CK references removed + */ +function removeAttackExternalReferences(externalReferences) { + if (!externalReferences || !Array.isArray(externalReferences)) { + return []; + } + return externalReferences.filter((ref) => !isAttackExternalReference(ref)); +} + +/** + * Validate that an ATT&CK external reference matches the expected values + * @param {object} actualRef - The actual external reference from client + * @param {object} expectedRef - The expected external reference + * @returns {object} Validation result with isValid boolean and error message if invalid + */ +function validateAttackExternalReference(actualRef, expectedRef) { + if (!actualRef || !expectedRef) { + return { isValid: true }; + } + + // Check external_id matches + if (actualRef.external_id !== expectedRef.external_id) { + return { + isValid: false, + error: `Cannot modify ATT&CK ID: expected '${expectedRef.external_id}' but received '${actualRef.external_id}'`, + }; + } + + // Check URL matches if expected URL exists + if (expectedRef.url && actualRef.url && actualRef.url !== expectedRef.url) { + return { + isValid: false, + error: `Cannot modify ATT&CK reference URL: expected '${expectedRef.url}' but received '${actualRef.url}'`, + }; + } + + return { isValid: true }; +} + +module.exports = { + buildAttackExternalReference, + createAttackExternalReference, + extractParentDetectionStrategyId, + isAttackExternalReference, + findAttackExternalReference, + removeAttackExternalReferences, + validateAttackExternalReference, +}; From b9d07b3dccf2c37ec060e5cf82f9504fed644e02 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:27:32 -0500 Subject: [PATCH 076/370] feat(analytics-service): override put method to ensure ext refs url is updated id parent det changes --- app/services/analytics-service.js | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/app/services/analytics-service.js b/app/services/analytics-service.js index 5d753247..3a1e0d77 100644 --- a/app/services/analytics-service.js +++ b/app/services/analytics-service.js @@ -5,8 +5,59 @@ const BaseService = require('./_base.service'); const { Analytic: AnalyticType } = require('../lib/types'); const detectionStrategiesService = require('./detection-strategies-service'); const dataComponentsService = require('./data-components-service'); +const { + createAttackExternalReference, + removeAttackExternalReferences, +} = require('../lib/external-reference-builder'); class AnalyticsService extends BaseService { + /** + * Override updateFull to ensure external_references URL is updated if parent detection strategy changes + * This can happen if the analytic's embedded_relationships are modified + */ + async updateFull(stixId, stixModified, data) { + // Get the existing document + const existingAnalytic = await this.repository.retrieveOneByVersion(stixId, stixModified); + if (!existingAnalytic) { + return null; + } + + // Check if embedded_relationships changed (specifically inbound detection strategy relationships) + const oldEmbeddedRels = existingAnalytic.workspace?.embedded_relationships || []; + const newEmbeddedRels = data.workspace?.embedded_relationships || []; + + const oldParentStrategy = oldEmbeddedRels.find( + (rel) => + rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--'), + ); + const newParentStrategy = newEmbeddedRels.find( + (rel) => + rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--'), + ); + + const parentStrategyChanged = + oldParentStrategy?.stix_id !== newParentStrategy?.stix_id || + oldParentStrategy?.attack_id !== newParentStrategy?.attack_id; + + // If parent detection strategy changed, rebuild the ATT&CK external reference + if (parentStrategyChanged && data.stix?.external_references) { + // Remove existing ATT&CK external references + data.stix.external_references = removeAttackExternalReferences(data.stix.external_references); + + // Create new ATT&CK external reference with updated URL + const attackRef = createAttackExternalReference({ + workspace: data.workspace, + stix: data.stix, + }); + + if (attackRef) { + data.stix.external_references.unshift(attackRef); + } + } + + // Call parent updateFull to handle all standard update logic + return await super.updateFull(stixId, stixModified, data); + } async retrieveAll(options) { const results = await super.retrieveAll(options); From 62518756c1c60c81fc11d36db2e5f5c151773a84 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:28:50 -0500 Subject: [PATCH 077/370] feat(det-strat-service): override create to maintain embedded relationships bidirectionally - When a detection strategy is created and references analytics via x_mitre_analytic_refs, - we need to update embedded_relationships on both the strategy (outbound) and the analytics (inbound) --- app/services/detection-strategies-service.js | 43 +++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/app/services/detection-strategies-service.js b/app/services/detection-strategies-service.js index c2d5be02..515c2529 100644 --- a/app/services/detection-strategies-service.js +++ b/app/services/detection-strategies-service.js @@ -1,10 +1,51 @@ 'use strict'; const detectionStrategiesRepository = require('../repository/detection-strategies-repository'); +const analyticsRepository = require('../repository/analytics-repository'); const BaseService = require('./_base.service'); const { DetectionStrategy: DetectionStrategyType } = require('../lib/types'); +const logger = require('../lib/logger'); -class DetectionStrategiesService extends BaseService {} +class DetectionStrategiesService extends BaseService { + /** + * Override create to maintain embedded_relationships bidirectionally + * When a detection strategy is created and references analytics via x_mitre_analytic_refs, + * we need to update embedded_relationships on both the strategy (outbound) and the analytics (inbound) + */ + async create(data, options) { + // Initialize embedded_relationships if not present + if (!data.workspace) { + data.workspace = {}; + } + if (!data.workspace.embedded_relationships) { + data.workspace.embedded_relationships = []; + } + + // Build outbound embedded_relationships for x_mitre_analytic_refs + const analyticRefs = data.stix?.x_mitre_analytic_refs || []; + if (analyticRefs.length > 0) { + const analyticEmbeddedRels = await this.buildEmbeddedRelationshipsForAnalytics(analyticRefs); + data.workspace.embedded_relationships.push(...analyticEmbeddedRels); + } + + // Call parent create to handle all standard creation logic + const createdStrategy = await super.create(data, options); + + // Update inbound embedded_relationships on referenced analytics + if (analyticRefs.length > 0) { + try { + await this.addInboundRelationshipsToAnalytics(analyticRefs, createdStrategy); + } catch (err) { + logger.error( + `Error updating embedded relationships for analytics after creating strategy ${createdStrategy.stix.id}:`, + err, + ); + // Don't fail the create operation, but log the error + } + } + + return createdStrategy; + } module.exports = new DetectionStrategiesService( DetectionStrategyType, From a636633db7ecfb338760ad361518297dc333d1b4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:30:13 -0500 Subject: [PATCH 078/370] feat(det-strat-service): override updateFull to maintain embedded relationships bidirectionally - When analytic refs chnage, updates embedded relationships on both objects - Also updates external refs URLs for analytics if the detection's ATT&CK ID changes --- app/services/detection-strategies-service.js | 289 +++++++++++++++++++ 1 file changed, 289 insertions(+) diff --git a/app/services/detection-strategies-service.js b/app/services/detection-strategies-service.js index 515c2529..a2b8f2de 100644 --- a/app/services/detection-strategies-service.js +++ b/app/services/detection-strategies-service.js @@ -47,6 +47,295 @@ class DetectionStrategiesService extends BaseService { return createdStrategy; } + /** + * Override updateFull to maintain embedded_relationships bidirectionally + * When x_mitre_analytic_refs changes, update embedded_relationships on both the strategy and affected analytics + * Also update external_references URLs for analytics if the strategy's ATT&CK ID changed + */ + async updateFull(stixId, stixModified, data) { + // Get the existing document to compare analytic refs + const existingStrategy = await this.repository.retrieveOneByVersion(stixId, stixModified); + if (!existingStrategy) { + return null; + } + + const oldAnalyticRefs = existingStrategy.stix.x_mitre_analytic_refs || []; + const newAnalyticRefs = data.stix?.x_mitre_analytic_refs || []; + const oldAttackId = existingStrategy.workspace?.attack_id; + const newAttackId = data.workspace?.attack_id; + + // Determine which analytics were added, removed, or unchanged + const addedAnalytics = newAnalyticRefs.filter((ref) => !oldAnalyticRefs.includes(ref)); + const removedAnalytics = oldAnalyticRefs.filter((ref) => !newAnalyticRefs.includes(ref)); + const unchangedAnalytics = newAnalyticRefs.filter((ref) => oldAnalyticRefs.includes(ref)); + + // Check if the strategy's ATT&CK ID changed + const attackIdChanged = oldAttackId !== newAttackId; + + // Update embedded_relationships in the data being saved + if (!data.workspace) { + data.workspace = {}; + } + if (!data.workspace.embedded_relationships) { + data.workspace.embedded_relationships = []; + } + + // Rebuild the analytic portion of embedded_relationships + const existingNonAnalyticRels = (data.workspace.embedded_relationships || []).filter( + (rel) => !rel.stix_id?.startsWith('x-mitre-analytic--'), + ); + const analyticEmbeddedRels = await this.buildEmbeddedRelationshipsForAnalytics(newAnalyticRefs); + data.workspace.embedded_relationships = [...existingNonAnalyticRels, ...analyticEmbeddedRels]; + + // Call parent updateFull to handle all standard update logic + const updatedStrategy = await super.updateFull(stixId, stixModified, data); + + // Update analytics' inbound embedded_relationships and external_references URLs + try { + // Add inbound relationships for newly added analytics + if (addedAnalytics.length > 0) { + await this.addInboundRelationshipsToAnalytics(addedAnalytics, updatedStrategy); + } + + // Remove inbound relationships for removed analytics + if (removedAnalytics.length > 0) { + await this.removeInboundRelationshipsFromAnalytics( + removedAnalytics, + updatedStrategy.stix.id, + ); + } + + // Update external_references URLs for analytics if the strategy's ATT&CK ID changed + if (attackIdChanged && unchangedAnalytics.length > 0) { + await this.updateAnalyticsExternalReferencesUrls(unchangedAnalytics, updatedStrategy); + } + } catch (err) { + logger.error( + `Error updating embedded relationships for analytics after updating strategy ${updatedStrategy.stix.id}:`, + err, + ); + // Don't fail the update operation, but log the error + } + + return updatedStrategy; + } + + /** + * Builds embedded_relationship objects for a list of analytic STIX IDs + * @param {string[]} analyticRefs - Array of analytic STIX IDs + * @returns {Promise} Array of embedded_relationship objects + */ + async buildEmbeddedRelationshipsForAnalytics(analyticRefs) { + const embeddedRels = []; + + for (const analyticId of analyticRefs) { + try { + // Fetch the latest version of the analytic to get its attack_id + const analytic = await analyticsRepository.retrieveLatestByStixIdLean(analyticId); + + if (analytic) { + embeddedRels.push({ + stix_id: analyticId, + attack_id: analytic.workspace?.attack_id || null, + direction: 'outbound', + }); + } else { + logger.warn(`Could not find analytic ${analyticId} when building embedded relationships`); + // Still add the relationship without attack_id + embeddedRels.push({ + stix_id: analyticId, + attack_id: null, + direction: 'outbound', + }); + } + } catch (err) { + logger.error(`Error fetching analytic ${analyticId}: ${err.message}`); + // Add relationship without attack_id + embeddedRels.push({ + stix_id: analyticId, + attack_id: null, + direction: 'outbound', + }); + } + } + + return embeddedRels; + } + + /** + * Adds inbound embedded_relationships to multiple analytics + * @param {string[]} analyticIds - Array of analytic STIX IDs + * @param {object} strategy - The detection strategy document + */ + async addInboundRelationshipsToAnalytics(analyticIds, strategy) { + for (const analyticId of analyticIds) { + try { + await this.addInboundRelationshipToAnalytic(analyticId, strategy); + } catch (err) { + logger.error(`Error adding inbound relationship to analytic ${analyticId}: ${err.message}`); + // Continue processing other analytics + } + } + } + + /** + * Adds an inbound embedded_relationship to an analytic and updates its external_references URL + * @param {string} analyticId - The STIX ID of the analytic + * @param {object} strategy - The detection strategy document + */ + async addInboundRelationshipToAnalytic(analyticId, strategy) { + const { + createAttackExternalReference, + removeAttackExternalReferences, + } = require('../lib/external-reference-builder'); + + // Get the latest version of the analytic as a Mongoose document + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + if (!analytic) { + logger.warn(`Could not find analytic ${analyticId} to add inbound relationship`); + return; + } + + // Initialize embedded_relationships if it doesn't exist + if (!analytic.workspace.embedded_relationships) { + analytic.workspace.embedded_relationships = []; + } + + // Check if this relationship already exists + const existingRel = analytic.workspace.embedded_relationships.find( + (rel) => rel.stix_id === strategy.stix.id && rel.direction === 'inbound', + ); + + if (!existingRel) { + // Add the inbound relationship + analytic.workspace.embedded_relationships.push({ + stix_id: strategy.stix.id, + attack_id: strategy.workspace?.attack_id || null, + direction: 'inbound', + }); + + // Update the analytic's external_references URL now that it has a parent detection strategy + if (analytic.stix.external_references) { + // Remove existing ATT&CK external references + analytic.stix.external_references = removeAttackExternalReferences( + analytic.stix.external_references, + ); + + // Create new ATT&CK external reference with the parent strategy's URL + const attackRef = createAttackExternalReference(analytic.toObject()); + if (attackRef) { + analytic.stix.external_references.unshift(attackRef); + } + } + + // Save the updated analytic using the repository method + await analyticsRepository.saveDocument(analytic); + } + } + + /** + * Removes inbound embedded_relationships from multiple analytics + * @param {string[]} analyticIds - Array of analytic STIX IDs + * @param {string} strategyId - The STIX ID of the detection strategy + */ + async removeInboundRelationshipsFromAnalytics(analyticIds, strategyId) { + for (const analyticId of analyticIds) { + try { + await this.removeInboundRelationshipFromAnalytic(analyticId, strategyId); + } catch (err) { + logger.error( + `Error removing inbound relationship from analytic ${analyticId}: ${err.message}`, + ); + // Continue processing other analytics + } + } + } + + /** + * Removes an inbound embedded_relationship from an analytic + * @param {string} analyticId - The STIX ID of the analytic + * @param {string} strategyId - The STIX ID of the detection strategy + */ + async removeInboundRelationshipFromAnalytic(analyticId, strategyId) { + // Get the latest version of the analytic as a Mongoose document + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + if (!analytic) { + logger.warn(`Could not find analytic ${analyticId} to remove inbound relationship`); + return; + } + + if (!analytic.workspace.embedded_relationships) { + return; // Nothing to remove + } + + // Remove the inbound relationship + analytic.workspace.embedded_relationships = analytic.workspace.embedded_relationships.filter( + (rel) => !(rel.stix_id === strategyId && rel.direction === 'inbound'), + ); + + // Save the updated analytic using the repository method + await analyticsRepository.saveDocument(analytic); + } + + /** + * Updates external_references URLs for analytics when their parent detection strategy's ATT&CK ID changes + * @param {string[]} analyticIds - Array of analytic STIX IDs + * @param {object} strategy - The updated detection strategy document + */ + async updateAnalyticsExternalReferencesUrls(analyticIds, strategy) { + const { + createAttackExternalReference, + removeAttackExternalReferences, + } = require('../lib/external-reference-builder'); + + for (const analyticId of analyticIds) { + try { + // Get the latest version of the analytic as a Mongoose document + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + if (!analytic) { + logger.warn(`Could not find analytic ${analyticId} to update external_references URL`); + continue; + } + + // Update the inbound relationship's attack_id in embedded_relationships + if (analytic.workspace.embedded_relationships) { + const inboundRel = analytic.workspace.embedded_relationships.find( + (rel) => rel.stix_id === strategy.stix.id && rel.direction === 'inbound', + ); + if (inboundRel) { + inboundRel.attack_id = strategy.workspace?.attack_id || null; + } + } + + // Rebuild the ATT&CK external reference with the new URL + if (analytic.stix.external_references) { + // Remove existing ATT&CK external references + analytic.stix.external_references = removeAttackExternalReferences( + analytic.stix.external_references, + ); + + // Create new ATT&CK external reference with updated URL + const attackRef = createAttackExternalReference(analytic.toObject()); + if (attackRef) { + analytic.stix.external_references.unshift(attackRef); + } + } + + // Save the updated analytic using the repository method + await analyticsRepository.saveDocument(analytic); + } catch (err) { + logger.error( + `Error updating external_references URL for analytic ${analyticId}: ${err.message}`, + ); + // Continue processing other analytics + } + } + } +} + module.exports = new DetectionStrategiesService( DetectionStrategyType, detectionStrategiesRepository, From fb704772848af1ae57deb3fbb679f5efde38f4fc Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:20:26 -0500 Subject: [PATCH 079/370] refactor: remove unnecessary app/services/index.js module - Module was importing and initializing stuff that wasn't being used - Deleting module has no impact on functionality --- app/services/index.js | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 app/services/index.js diff --git a/app/services/index.js b/app/services/index.js deleted file mode 100644 index ba4b67d3..00000000 --- a/app/services/index.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -//** import repositories */ -const attackObjectsRepository = require('../repository/attack-objects-repository'); -const identitiesRepository = require('../repository/identities-repository'); -const relationshipsRepository = require('../repository/relationships-repository'); - -//** imports services */ -const AttackObjectsService = require('./attack-objects-service'); -const { IdentitiesService } = require('./identities-service'); -const RelationshipsService = require('./relationships-service'); - -//** import types */ -const { Identity: IdentityType, Relationship: RelationshipType } = require('../lib/types'); - -// ** initialize services */ -const identitiesService = new IdentitiesService(IdentityType, identitiesRepository); -const relationshipsService = new RelationshipsService(RelationshipType, relationshipsRepository); -const attackObjectsService = new AttackObjectsService( - attackObjectsRepository, - identitiesService, - relationshipsService, -); - -module.exports = { - identitiesService, - relationshipsService, - attackObjectsService, -}; From 925dd68de223e31852da06abc9fa7412878561eb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:25:17 -0500 Subject: [PATCH 080/370] feat(abstract-service): add partial support for lifecycle hooks - Repurposing AbstractService to enable event-driven architecture - Service renamed to ServiceWithHooks - It will implement no-op lifecycle hook methods for SDO services to implement - It will also implement emitXEvent methods that the BaseService will call in its CRUD methods --- app/services/_abstract.service.js | 124 ++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 5 deletions(-) diff --git a/app/services/_abstract.service.js b/app/services/_abstract.service.js index 6fcc8398..2babe458 100644 --- a/app/services/_abstract.service.js +++ b/app/services/_abstract.service.js @@ -2,26 +2,140 @@ // Note there is a bug in eslint where single line comment will not work ^ 'use strict'; +const logger = require('../lib/logger'); +const EventBus = require('../lib/event-bus'); const { NotImplementedError } = require('../exceptions'); -class AbstractService { +class ServiceWithHooks { + /** ******************************** CREATE ******************************** */ + /** + * Lifecycle hook: Called before create() saves the document + * Subclasses can override to prepare data + * @param {object} _data - The data being created + * @param {object} _options - Creation options + */ + + async beforeCreate(_data, _options) { + // Default: no-op + } + + create(data, options) { + throw new NotImplementedError(this.constructor.name, 'create'); + } + + /** + * Lifecycle hook: Called after create() saves the document + * Subclasses can override to handle post-creation logic + * @param {object} _document - The created document + * @param {object} _options - Creation options + */ + + async afterCreate(_document, _options) { + // Default: no-op + } + + /** + * Emit event after document creation + * @param {object} document - The created document + * @param {object} options - Creation options + */ + async emitCreatedEvent(document, options) { + const eventName = `${this.type}::created`; + + logger.info(`Emitting event '${eventName}' for ${document.stix.id}`); + + await EventBus.emit(eventName, { + stixId: document.stix.id, + document: document.toObject ? document.toObject() : document, + type: this.type, + options, + }); + + logger.info(`Event '${eventName}' emission complete`); + } + + /** ******************************** READ ******************************** */ + /** ************ (these dont need hooks or event emitters) *************** */ + + // TODO add JSDoc retrieveAll(options) { throw new NotImplementedError(this.constructor.name, 'retrieveAll'); } + // TODO add JSDoc retrieveById(stixId, options) { throw new NotImplementedError(this.constructor.name, 'retrieveById'); } + // TODO add JSDoc retrieveVersionById(stixId, modified) { throw new NotImplementedError(this.constructor.name, 'retrieveVersionById'); } + /** ******************************** UPDATE ******************************** */ - create(data, options) { - throw new NotImplementedError(this.constructor.name, 'create'); + /** + * Lifecycle hook: Called before updateFull() saves the document + * Subclasses can override to prepare data + * @param {string} _stixId - The STIX ID + * @param {string} _stixModified - The modified timestamp + * @param {object} _data - The update data + * @param {object} _existingDocument - The existing document + */ + async beforeUpdate(_stixId, _stixModified, _data, _existingDocument) { + // Default: no-op + } + + // TODO add JSDoc + updateFull(stixId, stixModified, data) { + throw new NotImplementedError(this.constructor.name, 'updateFull'); + } + + /** + * Lifecycle hook: Called after updateFull() saves the document + * Subclasses can override to handle post-update logic + * @param {object} _updatedDocument - The updated document + * @param {object} _previousDocument - The previous document (before update) + */ + async afterUpdate(_updatedDocument, _previousDocument) { + // Default: no-op } - // ... other abstract methods ... + /** + * Emit event after document update + * @param {object} updatedDocument - The updated document + * @param {object} previousDocument - The previous document (before update) + */ + async emitUpdatedEvent(updatedDocument, previousDocument) { + const EventBus = require('../lib/event-bus'); + const eventName = `${this.type}::updated`; + await EventBus.emit(eventName, { + stixId: updatedDocument.stix.id, + stixModified: updatedDocument.stix.modified, + document: updatedDocument.toObject ? updatedDocument.toObject() : updatedDocument, + previousDocument: previousDocument.toObject ? previousDocument.toObject() : previousDocument, + type: this.type, + }); + } + + /** ******************************** DELETE ******************************** */ + // TODO there are multiple delete methods (e.g., deleteById, deleteVersionById): it is unclear whether they can/should share lifecycle hooks or have their own; if their own, then can the delete methods be consolidated? + + /** + * Lifecycle hook: Called before deleteVersionById() saves the document + * Subclasses can override to prepare data + * @param {string} _stixId - The STIX ID + * @param {string} _stixModified - The modified timestamp + * @param {object} _data - The update data + * @param {object} _existingDocument - The existing document + */ + + async beforeDeleteVersionById(_stixId, _stixModified, _data, _existingDocument) { + // Default: no-op + } + + async afterDeleteVersionById(_deletedDocument) { + /// Default: no-op + } } -module.exports = AbstractService; +module.exports = ServiceWithHooks; From 955798fb963b9c84fe887742980fa12c2b0db3d1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:29:13 -0500 Subject: [PATCH 081/370] build: update package-lock.json --- package-lock.json | 1023 +-------------------------------------------- 1 file changed, 8 insertions(+), 1015 deletions(-) diff --git a/package-lock.json b/package-lock.json index 67182b50..922bbb0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,977 +124,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-sdk/abort-controller": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.303.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-sdk-sts": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "fast-xml-parser": "4.1.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/config-resolver": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-config-provider": "3.295.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-ini": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/token-providers": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/querystring-builder": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/hash-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-buffer-from": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/service-error-classification": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "tslib": "^2.5.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-serde": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/signature-v4": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-config-provider": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/abort-controller": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/querystring-builder": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/property-provider": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/protocol-http": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-builder": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-uri-escape": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-parser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/service-error-classification": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-hex-encoding": "3.295.0", - "@aws-sdk/util-middleware": "3.303.0", - "@aws-sdk/util-uri-escape": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/url-parser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/querystring-parser": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-base64": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-config-provider": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-middleware": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-retry": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/service-error-classification": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, "node_modules/@babel/code-frame": { "version": "7.26.2", "dev": true, @@ -2231,7 +1260,6 @@ "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.1.2", @@ -2991,8 +2019,7 @@ }, "node_modules/@types/node": { "version": "14.11.8", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -3077,7 +2104,6 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3149,7 +2175,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3395,11 +2420,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bowser": { - "version": "2.11.0", - "license": "MIT", - "optional": true - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4187,7 +3207,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -4742,7 +3761,6 @@ "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4804,7 +3822,6 @@ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -5179,7 +4196,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5541,21 +4557,6 @@ "version": "3.0.3", "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.1.2", - "license": "MIT", - "optional": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -6675,11 +5676,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ip": { - "version": "2.0.0", - "license": "MIT", - "optional": true - }, "node_modules/ipaddr.js": { "version": "1.9.1", "license": "MIT", @@ -7468,7 +6464,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7943,7 +6938,6 @@ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.3", @@ -11842,7 +10836,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11916,6 +10909,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -12257,7 +11256,6 @@ "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -13010,11 +12008,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "license": "MIT", - "optional": true - }, "node_modules/super-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", @@ -13402,7 +12395,7 @@ }, "node_modules/tslib": { "version": "2.8.1", - "devOptional": true, + "dev": true, "license": "0BSD" }, "node_modules/tunnel": { From 6f462cb4cef1c856ecf54fe8c28fbf300bf76bd2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:24:18 -0500 Subject: [PATCH 082/370] feat(event-bus): implement event-bus singleton to enable event-driven arch in service layer --- app/lib/event-bus.js | 116 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 app/lib/event-bus.js diff --git a/app/lib/event-bus.js b/app/lib/event-bus.js new file mode 100644 index 00000000..8272ba53 --- /dev/null +++ b/app/lib/event-bus.js @@ -0,0 +1,116 @@ +'use strict'; + +const EventEmitter = require('events'); +const logger = require('./logger'); + +/** + * Event bus for decoupling service interactions + * Enables reactive, event-driven architecture for handling cross-document updates + * Built on Node.js EventEmitter + */ +class EventBus extends EventEmitter { + constructor() { + super(); + + // Increase max listeners since we may have multiple services subscribing to common events + this.setMaxListeners(50); + + // Event log for debugging + this.eventLog = []; + this.maxLogSize = 1000; + + // Track original on() for logging + this._originalOn = super.on.bind(this); + this._originalEmit = super.emit.bind(this); + } + + /** + * Override on() to add logging + * @param {string} eventName - Name of the event to listen for + * @param {Function} listener - Function to handle the event + * @returns {EventBus} this + */ + on(eventName, listener) { + const listenerName = listener.name || 'anonymous'; + logger.debug(`EventBus: Registered listener '${listenerName}' for '${eventName}'`); + return this._originalOn(eventName, listener); + } + + /** + * Override emit() to add logging and handle async listeners + * @param {string} eventName - Name of the event to emit + * @param {object} payload - Data to pass to event handlers + * @returns {Promise} + */ + async emit(eventName, payload) { + const timestamp = new Date().toISOString(); + + // Log the event + this.logEvent({ eventName, payload, timestamp }); + + logger.debug(`EventBus: Emitting '${eventName}'`); + + const listeners = this.listeners(eventName); + if (listeners.length === 0) { + logger.debug(`EventBus: No listeners for '${eventName}'`); + return; + } + + // Execute all listeners in parallel with Promise.allSettled + const results = await Promise.allSettled( + listeners.map(async (listener) => { + try { + const listenerName = listener.name || 'anonymous'; + logger.debug(`EventBus: Executing listener '${listenerName}' for '${eventName}'`); + await listener(payload); + logger.debug(`EventBus: Listener '${listenerName}' completed successfully`); + } catch (error) { + const listenerName = listener.name || 'anonymous'; + logger.error(`EventBus: Listener '${listenerName}' failed for '${eventName}':`, error); + throw error; + } + }), + ); + + // Log any failures + const failures = results.filter((r) => r.status === 'rejected'); + if (failures.length > 0) { + logger.warn( + `EventBus: ${failures.length}/${listeners.length} listeners failed for '${eventName}'`, + ); + } + } + + /** + * Log an event for debugging and auditing + * @param {object} event - Event details + */ + logEvent(event) { + this.eventLog.push(event); + + // Limit log size + if (this.eventLog.length > this.maxLogSize) { + this.eventLog.shift(); + } + } + + /** + * Get recent events for debugging + * @param {number} limit - Number of recent events to return + * @returns {Array} Recent events + */ + getRecentEvents(limit = 50) { + return this.eventLog.slice(-limit); + } + + /** + * Clear all listeners (useful for testing) + */ + clear() { + this.removeAllListeners(); + logger.debug('EventBus: Cleared all listeners'); + } +} + +// Export singleton instance +module.exports = new EventBus(); From cab5af3c18e02ebd1998aad554d639d0002c8dd3 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:25:04 -0500 Subject: [PATCH 083/370] feat(event-bus): add event-constants.js to centrally manage and define supported event messages --- app/lib/event-constants.js | 128 +++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 app/lib/event-constants.js diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js new file mode 100644 index 00000000..f2ccff85 --- /dev/null +++ b/app/lib/event-constants.js @@ -0,0 +1,128 @@ +'use strict'; + +/** + * Central enumeration of all events supported on the EventBus + * Use these constants instead of string literals to ensure consistency and enable IDE autocomplete + * + * Event Naming Convention: ::[.] + * - subject: Entity type (lowercase, singular or hyphenated) + * - action: Operation performed (past tense verb) + * - detail: Optional qualifier for specialized events + */ + +module.exports = Object.freeze({ + // ============================================================================ + // Core CRUD Events + // Emitted by BaseService after lifecycle hooks complete + // ============================================================================ + + // Attack Patterns (Techniques & Sub-techniques) + ATTACK_PATTERN_CREATED: 'attack-pattern::created', + ATTACK_PATTERN_UPDATED: 'attack-pattern::updated', + ATTACK_PATTERN_DELETED: 'attack-pattern::deleted', + + // Tactics + TACTIC_CREATED: 'x-mitre-tactic::created', + TACTIC_UPDATED: 'x-mitre-tactic::updated', + TACTIC_DELETED: 'x-mitre-tactic::deleted', + + // Mitigations + COURSE_OF_ACTION_CREATED: 'course-of-action::created', + COURSE_OF_ACTION_UPDATED: 'course-of-action::updated', + COURSE_OF_ACTION_DELETED: 'course-of-action::deleted', + + // Groups + INTRUSION_SET_CREATED: 'intrusion-set::created', + INTRUSION_SET_UPDATED: 'intrusion-set::updated', + INTRUSION_SET_DELETED: 'intrusion-set::deleted', + + // Software + MALWARE_CREATED: 'malware::created', + MALWARE_UPDATED: 'malware::updated', + MALWARE_DELETED: 'malware::deleted', + + TOOL_CREATED: 'tool::created', + TOOL_UPDATED: 'tool::updated', + TOOL_DELETED: 'tool::deleted', + + // Campaigns + CAMPAIGN_CREATED: 'campaign::created', + CAMPAIGN_UPDATED: 'campaign::updated', + CAMPAIGN_DELETED: 'campaign::deleted', + + // Data Sources + DATA_SOURCE_CREATED: 'x-mitre-data-source::created', + DATA_SOURCE_UPDATED: 'x-mitre-data-source::updated', + DATA_SOURCE_DELETED: 'x-mitre-data-source::deleted', + + // Data Components + DATA_COMPONENT_CREATED: 'x-mitre-data-component::created', + DATA_COMPONENT_UPDATED: 'x-mitre-data-component::updated', + DATA_COMPONENT_DELETED: 'x-mitre-data-component::deleted', + + // Matrices + MATRIX_CREATED: 'x-mitre-matrix::created', + MATRIX_UPDATED: 'x-mitre-matrix::updated', + MATRIX_DELETED: 'x-mitre-matrix::deleted', + + // Collections + COLLECTION_CREATED: 'x-mitre-collection::created', + COLLECTION_UPDATED: 'x-mitre-collection::updated', + COLLECTION_DELETED: 'x-mitre-collection::deleted', + + // Detection Strategies + DETECTION_STRATEGY_CREATED: 'x-mitre-detection-strategy::created', + DETECTION_STRATEGY_UPDATED: 'x-mitre-detection-strategy::updated', + DETECTION_STRATEGY_DELETED: 'x-mitre-detection-strategy::deleted', + + // Analytics + ANALYTIC_CREATED: 'x-mitre-analytic::created', + ANALYTIC_UPDATED: 'x-mitre-analytic::updated', + ANALYTIC_DELETED: 'x-mitre-analytic::deleted', + + // Assets + ASSET_CREATED: 'x-mitre-asset::created', + ASSET_UPDATED: 'x-mitre-asset::updated', + ASSET_DELETED: 'x-mitre-asset::deleted', + + // ============================================================================ + // Cross-Document Events + // Emitted by Manager classes when changes affect multiple documents + // ============================================================================ + + // Embedded Relationships + EMBEDDED_RELATIONSHIP_ADDED: 'embedded-relationship::added', + EMBEDDED_RELATIONSHIP_REMOVED: 'embedded-relationship::removed', + EMBEDDED_RELATIONSHIP_UPDATED: 'embedded-relationship::updated', + + // ATT&CK IDs + ATTACK_ID_ASSIGNED: 'attack-id::assigned', + ATTACK_ID_CHANGED: 'attack-id::changed', + ATTACK_ID_REMOVED: 'attack-id::removed', + + // External References + EXTERNAL_REFERENCE_ADDED: 'external-reference::added', + EXTERNAL_REFERENCE_REMOVED: 'external-reference::removed', + EXTERNAL_REFERENCE_UPDATED: 'external-reference::updated', + + // ============================================================================ + // Specialized Domain Events + // Emitted by SDO services for significant domain-specific changes + // ============================================================================ + + // Detection Strategy - Analytics relationship + DETECTION_STRATEGY_ANALYTICS_CHANGED: 'detection-strategy::analytics-changed', + + // Technique - Sub-technique conversion + TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE: 'attack-pattern::converted-to-subtechnique', + SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE: 'attack-pattern::converted-to-technique', + + // Data Source - Data Component relationship + DATA_SOURCE_COMPONENTS_CHANGED: 'x-mitre-data-source::components-changed', + + // Matrix - Tactics relationship + MATRIX_TACTICS_CHANGED: 'x-mitre-matrix::tactics-changed', + + // Collection - Objects relationship + COLLECTION_OBJECTS_CHANGED: 'x-mitre-collection::objects-changed', +}); From 511a01ee56b979f06ae65e715077afc0935eed27 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:26:13 -0500 Subject: [PATCH 084/370] feat(BaseService): implement lifecycle hooks for POST and PUT endpoints --- app/services/_base.service.js | 43 ++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/app/services/_base.service.js b/app/services/_base.service.js index 54e5addd..70f47d1e 100644 --- a/app/services/_base.service.js +++ b/app/services/_base.service.js @@ -19,14 +19,14 @@ const { ImmutablePropertyError, InvalidPostOperationError, } = require('../exceptions'); -const AbstractService = require('./_abstract.service'); +const ServiceWithHooks = require('./_abstract.service'); // Import required repositories const systemConfigurationRepository = require('../repository/system-configurations-repository'); const identitiesRepository = require('../repository/identities-repository'); const userAccountsService = require('./user-accounts-service'); -class BaseService extends AbstractService { +class BaseService extends ServiceWithHooks { constructor(type, repository) { super(); this.type = type; @@ -315,12 +315,19 @@ class BaseService extends AbstractService { return documents; } + // TODO add JSDoc + // explain what the method handles + // calls beforeCreate --> {own create logic} --> afterCreate --> emitCreatedEvent async create(data, options) { if (data?.stix?.type !== this.type) { throw new InvalidTypeError(); } options = options || {}; + + // LIFECYCLE HOOK: beforeCreate + // Subclasses can prepare data before core creation logic + await this.beforeCreate(data, options); if (!options.import) { const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( data.stix, @@ -394,17 +401,19 @@ class BaseService extends AbstractService { if (attackRef) { data.stix.external_references.unshift(attackRef); } - console.debug('Generated and set the MITRE ATT&CK external reference:', attackRef); + logger.debug(`Generated and set the MITRE ATT&CK external reference: ${attackRef}`); // Set the ATT&CK Spec Version data.stix.x_mitre_attack_spec_version = data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; - console.debug('Set the ATT&CK specification version:', data.stix.x_mitre_attack_spec_version); + logger.debug( + `Set the ATT&CK specification version: ${data.stix.x_mitre_attack_spec_version}`, + ); // Record the user account that created the object if (options.userAccountId) { data.workspace.workflow.created_by_user_account = options.userAccountId; - console.debug('Recorded the user account that created the object:', options.userAccountId); + logger.debug(`Recorded the user account that created the object: ${options.userAccountId}`); } // Set the default marking definitions @@ -435,7 +444,18 @@ class BaseService extends AbstractService { data.stix.x_mitre_modified_by_ref = organizationIdentityRef; } } - return await this.repository.save(data); + + // Core creation: Save the document + const createdDocument = await this.repository.save(data); + + // LIFECYCLE HOOK: afterCreate + // Subclasses can handle post-creation logic + await this.afterCreate(createdDocument, options); + + // EVENT EMISSION: Emit created event for other services to react + await this.emitCreatedEvent(createdDocument, options); + + return createdDocument; } async updateFull(stixId, stixModified, data) { @@ -452,6 +472,10 @@ class BaseService extends AbstractService { return null; } + // LIFECYCLE HOOK: beforeUpdate + // Subclasses can prepare data before core update logic + await this.beforeUpdate(stixId, stixModified, data, document); + // Handle ATT&CK external reference for UPDATE operations // On update, clients CAN provide ATT&CK external references, but they must match the existing data // and cannot be modified @@ -490,6 +514,13 @@ class BaseService extends AbstractService { const newDocument = await this.repository.updateAndSave(document, data); if (newDocument === document) { + // LIFECYCLE HOOK: afterUpdate + // Subclasses can handle post-update logic + await this.afterUpdate(newDocument, document); + + // EVENT EMISSION: Emit updated event for other services to react + await this.emitUpdatedEvent(newDocument, document); + // Document successfully saved return newDocument; } else { From 989ce023ae1d064cc3f7212cf99692e323562bd8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:28:21 -0500 Subject: [PATCH 085/370] feat(AnalyticsService): add event handlers that run when analytics are (de)referenced by detections --- app/services/analytics-service.js | 175 ++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/app/services/analytics-service.js b/app/services/analytics-service.js index 3a1e0d77..d2492bee 100644 --- a/app/services/analytics-service.js +++ b/app/services/analytics-service.js @@ -9,8 +9,181 @@ const { createAttackExternalReference, removeAttackExternalReferences, } = require('../lib/external-reference-builder'); +const EventBus = require('../lib/event-bus'); +const logger = require('../lib/logger'); +/** + * Service for managing analytics + * + * Event listeners: + * - x-mitre-detection-strategy::analytics-referenced - Add inbound relationships when detection strategy references analytics + * - x-mitre-detection-strategy::analytics-removed - Remove inbound relationships when detection strategy removes analytics + */ class AnalyticsService extends BaseService { + /** + * Initialize event listeners + * Called once on app startup + */ + static initializeEventListeners() { + EventBus.on( + 'x-mitre-detection-strategy::analytics-referenced', + this.handleAnalyticsReferenced.bind(this), + ); + + EventBus.on( + 'x-mitre-detection-strategy::analytics-removed', + this.handleAnalyticsRemoved.bind(this), + ); + + logger.info('AnalyticsService: Event listeners initialized'); + } + + /** + * Handle analytics being referenced by a detection strategy + * Add inbound embedded_relationship and update external_references + */ + static async handleAnalyticsReferenced(payload) { + const { detectionStrategy, analyticIds } = payload; + + for (const analyticId of analyticIds) { + try { + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + if (!analytic) { + logger.warn( + `AnalyticsService: Could not find analytic ${analyticId} to add inbound relationship`, + ); + continue; + } + + // Initialize embedded_relationships if needed + if (!analytic.workspace) { + analytic.workspace = {}; + } + if (!analytic.workspace.embedded_relationships) { + analytic.workspace.embedded_relationships = []; + } + + // Check if relationship already exists + const exists = analytic.workspace.embedded_relationships.some( + (rel) => rel.stix_id === detectionStrategy.stix.id && rel.direction === 'inbound', + ); + + if (!exists) { + // Add inbound embedded_relationship + analytic.workspace.embedded_relationships.push({ + stix_id: detectionStrategy.stix.id, + attack_id: detectionStrategy.workspace?.attack_id || null, + direction: 'inbound', + }); + + logger.info( + `AnalyticsService: Added inbound relationship from detection strategy ${detectionStrategy.stix.id} to analytic ${analyticId}`, + ); + } + + // Update external_references with URL to parent detection strategy + if (!analytic.stix.external_references) { + analytic.stix.external_references = []; + } + + // Remove existing ATT&CK external references + analytic.stix.external_references = removeAttackExternalReferences( + analytic.stix.external_references, + ); + + // Create new ATT&CK external reference with URL + const attackRef = createAttackExternalReference(analytic.toObject()); + if (attackRef) { + analytic.stix.external_references.unshift(attackRef); + logger.info( + `AnalyticsService: Updated external_references URL for analytic ${analyticId}`, + ); + } + + await analyticsRepository.saveDocument(analytic); + } catch (error) { + logger.error( + `AnalyticsService: Error handling analytics-referenced for ${analyticId}:`, + error, + ); + // Continue processing other analytics + } + } + } + + /** + * Handle analytics being removed from a detection strategy + * Remove inbound embedded_relationship and update external_references + */ + static async handleAnalyticsRemoved(payload) { + const { detectionStrategyId, analyticIds } = payload; + + for (const analyticId of analyticIds) { + try { + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + if (!analytic) { + logger.warn( + `AnalyticsService: Could not find analytic ${analyticId} to remove inbound relationship`, + ); + continue; + } + + if (analytic.workspace?.embedded_relationships) { + // Remove inbound embedded_relationship + const initialLength = analytic.workspace.embedded_relationships.length; + analytic.workspace.embedded_relationships = + analytic.workspace.embedded_relationships.filter( + (rel) => !(rel.stix_id === detectionStrategyId && rel.direction === 'inbound'), + ); + + const removed = analytic.workspace.embedded_relationships.length < initialLength; + if (removed) { + logger.info( + `AnalyticsService: Removed inbound relationship from detection strategy ${detectionStrategyId} to analytic ${analyticId}`, + ); + } + } + + // Update external_references (remove URL since no parent) + if (analytic.stix?.external_references) { + // analytic.stix.external_references = removeAttackExternalReferences( + // analytic.stix.external_references, + // ); + + // Rebuild external reference without URL (no parent detection strategy) + const existingAttackRef = + analytic.stix.external_references.find( + (ref) => ref && ref.source_name === 'mitre-attack', + ) || null; + + if (existingAttackRef) delete existingAttackRef.url; + + // const attackRef = { + // source_name: 'mitre-attack', + // external_id: analytic.workspace.attack_id, + // }; + // const attackRef = createAttackExternalReference(analytic.toObject()); + // if (attackRef) { + // analytic.stix.external_references.unshift(attackRef); + // } + + logger.info( + `AnalyticsService: Removed external_references URL for analytic ${analyticId}`, + ); + } + + await analyticsRepository.saveDocument(analytic); + } catch (error) { + logger.error( + `AnalyticsService: Error handling analytics-removed for ${analyticId}:`, + error, + ); + // Continue processing other analytics + } + } + } /** * Override updateFull to ensure external_references URL is updated if parent detection strategy changes * This can happen if the analytic's embedded_relationships are modified @@ -191,4 +364,6 @@ class AnalyticsService extends BaseService { } } +AnalyticsService.initializeEventListeners(); + module.exports = new AnalyticsService(AnalyticType, analyticsRepository); From 823113bd63880c31733fadd64a34a5f95ae1420d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:29:23 -0500 Subject: [PATCH 086/370] feat(DetectionStrategiesService): implement lifecycle hooks to manage embedded rels with analytics --- app/services/detection-strategies-service.js | 375 ++++++------------- 1 file changed, 105 insertions(+), 270 deletions(-) diff --git a/app/services/detection-strategies-service.js b/app/services/detection-strategies-service.js index a2b8f2de..1c17d9ec 100644 --- a/app/services/detection-strategies-service.js +++ b/app/services/detection-strategies-service.js @@ -5,14 +5,27 @@ const analyticsRepository = require('../repository/analytics-repository'); const BaseService = require('./_base.service'); const { DetectionStrategy: DetectionStrategyType } = require('../lib/types'); const logger = require('../lib/logger'); - +const EventBus = require('../lib/event-bus'); + +/** + * Service for managing detection strategies + * + * Lifecycle hooks: + * - beforeCreate: Builds outbound embedded_relationships for x_mitre_analytic_refs + * - afterCreate: Emits domain event to notify AnalyticsService + * - beforeUpdate: Updates outbound embedded_relationships when refs change + * - afterUpdate: Emits domain events for added/removed analytics + * + * Events emitted (listened to by AnalyticsService): + * - x-mitre-detection-strategy::analytics-referenced + * - x-mitre-detection-strategy::analytics-removed + */ class DetectionStrategiesService extends BaseService { /** - * Override create to maintain embedded_relationships bidirectionally - * When a detection strategy is created and references analytics via x_mitre_analytic_refs, - * we need to update embedded_relationships on both the strategy (outbound) and the analytics (inbound) + * Prepare detection strategy data before creation + * Build outbound embedded_relationships for x_mitre_analytic_refs */ - async create(data, options) { + async beforeCreate(data) { // Initialize embedded_relationships if not present if (!data.workspace) { data.workspace = {}; @@ -23,54 +36,61 @@ class DetectionStrategiesService extends BaseService { // Build outbound embedded_relationships for x_mitre_analytic_refs const analyticRefs = data.stix?.x_mitre_analytic_refs || []; - if (analyticRefs.length > 0) { - const analyticEmbeddedRels = await this.buildEmbeddedRelationshipsForAnalytics(analyticRefs); - data.workspace.embedded_relationships.push(...analyticEmbeddedRels); - } - - // Call parent create to handle all standard creation logic - const createdStrategy = await super.create(data, options); - - // Update inbound embedded_relationships on referenced analytics - if (analyticRefs.length > 0) { + for (const analyticId of analyticRefs) { try { - await this.addInboundRelationshipsToAnalytics(analyticRefs, createdStrategy); - } catch (err) { - logger.error( - `Error updating embedded relationships for analytics after creating strategy ${createdStrategy.stix.id}:`, - err, + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: analytic?.workspace?.attack_id || null, + direction: 'outbound', + }); + } catch (error) { + logger.warn( + `DetectionStrategiesService: Could not fetch analytic ${analyticId} for outbound relationship`, + error, ); - // Don't fail the create operation, but log the error + // Add relationship without attack_id + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: null, + direction: 'outbound', + }); } } - - return createdStrategy; } /** - * Override updateFull to maintain embedded_relationships bidirectionally - * When x_mitre_analytic_refs changes, update embedded_relationships on both the strategy and affected analytics - * Also update external_references URLs for analytics if the strategy's ATT&CK ID changed + * Handle post-creation logic + * Emit domain event to notify AnalyticsService that analytics were referenced */ - async updateFull(stixId, stixModified, data) { - // Get the existing document to compare analytic refs - const existingStrategy = await this.repository.retrieveOneByVersion(stixId, stixModified); - if (!existingStrategy) { - return null; + async afterCreate(document) { + const analyticRefs = document.stix?.x_mitre_analytic_refs || []; + + if (analyticRefs.length > 0) { + logger.info( + `DetectionStrategiesService: Emitting analytics-referenced event for ${analyticRefs.length} analytic(s)`, + { stixId: document.stix.id, analyticIds: analyticRefs }, + ); + + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategyId: document.stix.id, + detectionStrategy: document.toObject ? document.toObject() : document, + analyticIds: analyticRefs, + }); } + } - const oldAnalyticRefs = existingStrategy.stix.x_mitre_analytic_refs || []; + /** + * Prepare detection strategy data before update + * Detect changes in x_mitre_analytic_refs and update outbound embedded_relationships + */ + async beforeUpdate(stixId, stixModified, data, existingDocument) { + const oldAnalyticRefs = existingDocument.stix?.x_mitre_analytic_refs || []; const newAnalyticRefs = data.stix?.x_mitre_analytic_refs || []; - const oldAttackId = existingStrategy.workspace?.attack_id; - const newAttackId = data.workspace?.attack_id; - - // Determine which analytics were added, removed, or unchanged - const addedAnalytics = newAnalyticRefs.filter((ref) => !oldAnalyticRefs.includes(ref)); - const removedAnalytics = oldAnalyticRefs.filter((ref) => !newAnalyticRefs.includes(ref)); - const unchangedAnalytics = newAnalyticRefs.filter((ref) => oldAnalyticRefs.includes(ref)); - // Check if the strategy's ATT&CK ID changed - const attackIdChanged = oldAttackId !== newAttackId; + // Store change detection for afterUpdate + this._addedAnalyticRefs = newAnalyticRefs.filter((ref) => !oldAnalyticRefs.includes(ref)); + this._removedAnalyticRefs = oldAnalyticRefs.filter((ref) => !newAnalyticRefs.includes(ref)); // Update embedded_relationships in the data being saved if (!data.workspace) { @@ -84,74 +104,22 @@ class DetectionStrategiesService extends BaseService { const existingNonAnalyticRels = (data.workspace.embedded_relationships || []).filter( (rel) => !rel.stix_id?.startsWith('x-mitre-analytic--'), ); - const analyticEmbeddedRels = await this.buildEmbeddedRelationshipsForAnalytics(newAnalyticRefs); - data.workspace.embedded_relationships = [...existingNonAnalyticRels, ...analyticEmbeddedRels]; - - // Call parent updateFull to handle all standard update logic - const updatedStrategy = await super.updateFull(stixId, stixModified, data); - - // Update analytics' inbound embedded_relationships and external_references URLs - try { - // Add inbound relationships for newly added analytics - if (addedAnalytics.length > 0) { - await this.addInboundRelationshipsToAnalytics(addedAnalytics, updatedStrategy); - } - - // Remove inbound relationships for removed analytics - if (removedAnalytics.length > 0) { - await this.removeInboundRelationshipsFromAnalytics( - removedAnalytics, - updatedStrategy.stix.id, - ); - } - - // Update external_references URLs for analytics if the strategy's ATT&CK ID changed - if (attackIdChanged && unchangedAnalytics.length > 0) { - await this.updateAnalyticsExternalReferencesUrls(unchangedAnalytics, updatedStrategy); - } - } catch (err) { - logger.error( - `Error updating embedded relationships for analytics after updating strategy ${updatedStrategy.stix.id}:`, - err, - ); - // Don't fail the update operation, but log the error - } - - return updatedStrategy; - } - /** - * Builds embedded_relationship objects for a list of analytic STIX IDs - * @param {string[]} analyticRefs - Array of analytic STIX IDs - * @returns {Promise} Array of embedded_relationship objects - */ - async buildEmbeddedRelationshipsForAnalytics(analyticRefs) { - const embeddedRels = []; - - for (const analyticId of analyticRefs) { + const analyticEmbeddedRels = []; + for (const analyticId of newAnalyticRefs) { try { - // Fetch the latest version of the analytic to get its attack_id - const analytic = await analyticsRepository.retrieveLatestByStixIdLean(analyticId); - - if (analytic) { - embeddedRels.push({ - stix_id: analyticId, - attack_id: analytic.workspace?.attack_id || null, - direction: 'outbound', - }); - } else { - logger.warn(`Could not find analytic ${analyticId} when building embedded relationships`); - // Still add the relationship without attack_id - embeddedRels.push({ - stix_id: analyticId, - attack_id: null, - direction: 'outbound', - }); - } - } catch (err) { - logger.error(`Error fetching analytic ${analyticId}: ${err.message}`); - // Add relationship without attack_id - embeddedRels.push({ + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + analyticEmbeddedRels.push({ + stix_id: analyticId, + attack_id: analytic?.workspace?.attack_id || null, + direction: 'outbound', + }); + } catch (error) { + logger.warn( + `DetectionStrategiesService: Could not fetch analytic ${analyticId} for outbound relationship`, + error, + ); + analyticEmbeddedRels.push({ stix_id: analyticId, attack_id: null, direction: 'outbound', @@ -159,180 +127,47 @@ class DetectionStrategiesService extends BaseService { } } - return embeddedRels; - } - - /** - * Adds inbound embedded_relationships to multiple analytics - * @param {string[]} analyticIds - Array of analytic STIX IDs - * @param {object} strategy - The detection strategy document - */ - async addInboundRelationshipsToAnalytics(analyticIds, strategy) { - for (const analyticId of analyticIds) { - try { - await this.addInboundRelationshipToAnalytic(analyticId, strategy); - } catch (err) { - logger.error(`Error adding inbound relationship to analytic ${analyticId}: ${err.message}`); - // Continue processing other analytics - } - } + data.workspace.embedded_relationships = [...existingNonAnalyticRels, ...analyticEmbeddedRels]; } /** - * Adds an inbound embedded_relationship to an analytic and updates its external_references URL - * @param {string} analyticId - The STIX ID of the analytic - * @param {object} strategy - The detection strategy document + * Handle post-update logic + * Emit domain events for analytics that were added or removed */ - async addInboundRelationshipToAnalytic(analyticId, strategy) { - const { - createAttackExternalReference, - removeAttackExternalReferences, - } = require('../lib/external-reference-builder'); - - // Get the latest version of the analytic as a Mongoose document - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - - if (!analytic) { - logger.warn(`Could not find analytic ${analyticId} to add inbound relationship`); - return; - } - - // Initialize embedded_relationships if it doesn't exist - if (!analytic.workspace.embedded_relationships) { - analytic.workspace.embedded_relationships = []; - } - - // Check if this relationship already exists - const existingRel = analytic.workspace.embedded_relationships.find( - (rel) => rel.stix_id === strategy.stix.id && rel.direction === 'inbound', - ); + async afterUpdate(updatedDocument) { + const addedRefs = this._addedAnalyticRefs || []; + const removedRefs = this._removedAnalyticRefs || []; + + // Emit event for newly referenced analytics + if (addedRefs.length > 0) { + logger.info( + `DetectionStrategiesService: Emitting analytics-referenced event for ${addedRefs.length} added analytic(s)`, + { stixId: updatedDocument.stix.id, analyticIds: addedRefs }, + ); - if (!existingRel) { - // Add the inbound relationship - analytic.workspace.embedded_relationships.push({ - stix_id: strategy.stix.id, - attack_id: strategy.workspace?.attack_id || null, - direction: 'inbound', + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategyId: updatedDocument.stix.id, + detectionStrategy: updatedDocument.toObject ? updatedDocument.toObject() : updatedDocument, + analyticIds: addedRefs, }); - - // Update the analytic's external_references URL now that it has a parent detection strategy - if (analytic.stix.external_references) { - // Remove existing ATT&CK external references - analytic.stix.external_references = removeAttackExternalReferences( - analytic.stix.external_references, - ); - - // Create new ATT&CK external reference with the parent strategy's URL - const attackRef = createAttackExternalReference(analytic.toObject()); - if (attackRef) { - analytic.stix.external_references.unshift(attackRef); - } - } - - // Save the updated analytic using the repository method - await analyticsRepository.saveDocument(analytic); } - } - /** - * Removes inbound embedded_relationships from multiple analytics - * @param {string[]} analyticIds - Array of analytic STIX IDs - * @param {string} strategyId - The STIX ID of the detection strategy - */ - async removeInboundRelationshipsFromAnalytics(analyticIds, strategyId) { - for (const analyticId of analyticIds) { - try { - await this.removeInboundRelationshipFromAnalytic(analyticId, strategyId); - } catch (err) { - logger.error( - `Error removing inbound relationship from analytic ${analyticId}: ${err.message}`, - ); - // Continue processing other analytics - } - } - } - - /** - * Removes an inbound embedded_relationship from an analytic - * @param {string} analyticId - The STIX ID of the analytic - * @param {string} strategyId - The STIX ID of the detection strategy - */ - async removeInboundRelationshipFromAnalytic(analyticId, strategyId) { - // Get the latest version of the analytic as a Mongoose document - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - - if (!analytic) { - logger.warn(`Could not find analytic ${analyticId} to remove inbound relationship`); - return; - } + // Emit event for removed analytics + if (removedRefs.length > 0) { + logger.info( + `DetectionStrategiesService: Emitting analytics-removed event for ${removedRefs.length} removed analytic(s)`, + { stixId: updatedDocument.stix.id, analyticIds: removedRefs }, + ); - if (!analytic.workspace.embedded_relationships) { - return; // Nothing to remove + await EventBus.emit('x-mitre-detection-strategy::analytics-removed', { + detectionStrategyId: updatedDocument.stix.id, + analyticIds: removedRefs, + }); } - // Remove the inbound relationship - analytic.workspace.embedded_relationships = analytic.workspace.embedded_relationships.filter( - (rel) => !(rel.stix_id === strategyId && rel.direction === 'inbound'), - ); - - // Save the updated analytic using the repository method - await analyticsRepository.saveDocument(analytic); - } - - /** - * Updates external_references URLs for analytics when their parent detection strategy's ATT&CK ID changes - * @param {string[]} analyticIds - Array of analytic STIX IDs - * @param {object} strategy - The updated detection strategy document - */ - async updateAnalyticsExternalReferencesUrls(analyticIds, strategy) { - const { - createAttackExternalReference, - removeAttackExternalReferences, - } = require('../lib/external-reference-builder'); - - for (const analyticId of analyticIds) { - try { - // Get the latest version of the analytic as a Mongoose document - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - - if (!analytic) { - logger.warn(`Could not find analytic ${analyticId} to update external_references URL`); - continue; - } - - // Update the inbound relationship's attack_id in embedded_relationships - if (analytic.workspace.embedded_relationships) { - const inboundRel = analytic.workspace.embedded_relationships.find( - (rel) => rel.stix_id === strategy.stix.id && rel.direction === 'inbound', - ); - if (inboundRel) { - inboundRel.attack_id = strategy.workspace?.attack_id || null; - } - } - - // Rebuild the ATT&CK external reference with the new URL - if (analytic.stix.external_references) { - // Remove existing ATT&CK external references - analytic.stix.external_references = removeAttackExternalReferences( - analytic.stix.external_references, - ); - - // Create new ATT&CK external reference with updated URL - const attackRef = createAttackExternalReference(analytic.toObject()); - if (attackRef) { - analytic.stix.external_references.unshift(attackRef); - } - } - - // Save the updated analytic using the repository method - await analyticsRepository.saveDocument(analytic); - } catch (err) { - logger.error( - `Error updating external_references URL for analytic ${analyticId}: ${err.message}`, - ); - // Continue processing other analytics - } - } + // Clean up instance variables + delete this._addedAnalyticRefs; + delete this._removedAnalyticRefs; } } From 1d3c87f806c2fa4e24a8bd68bad40dd42702556f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:33:31 -0500 Subject: [PATCH 087/370] docs: add project architecture design and impl guides --- EVENT_BUS_ARCHITECTURE.md | 449 +++++++++++++++++++++++++++++++++++++ IMPLEMENTATION_APPROACH.md | 283 +++++++++++++++++++++++ LIFECYCLE_HOOKS_GUIDE.md | 396 ++++++++++++++++++++++++++++++++ 3 files changed, 1128 insertions(+) create mode 100644 EVENT_BUS_ARCHITECTURE.md create mode 100644 IMPLEMENTATION_APPROACH.md create mode 100644 LIFECYCLE_HOOKS_GUIDE.md diff --git a/EVENT_BUS_ARCHITECTURE.md b/EVENT_BUS_ARCHITECTURE.md new file mode 100644 index 00000000..db52f87f --- /dev/null +++ b/EVENT_BUS_ARCHITECTURE.md @@ -0,0 +1,449 @@ +# Event Bus Architecture Design + +## Overview + +This document defines the event-driven architecture for managing cross-document dependencies and derived properties in the ATT&CK Workbench REST API. + +## Core Principles + +### 1. Lifecycle Hooks Pattern + +Each CRUD operation has exactly **three lifecycle stages**: + +``` +beforeX → X → afterX → emitXEvent +``` + +For example: +- `beforeCreate` → `create` → `afterCreate` → `emitCreatedEvent` +- `beforeUpdate` → `update` → `afterUpdate` → `emitUpdatedEvent` +- `beforeDelete` → `delete` → `afterDelete` → `emitDeletedEvent` + +**Execution Order:** + +1. **`beforeX` hook** - Child service can modify data before persistence +2. **`X` operation** - BaseService persists the Mongoose document to database +3. **`afterX` hook** - Child service can perform side-effects after persistence +4. **`emitXEvent` method** - BaseService emits event to EventBus for cross-service coordination + +**Key Rules:** + +- The terms "before" and "after" refer explicitly to **database persistence** +- Child services override `beforeX` and `afterX` hooks, NOT the main operation +- BaseService orchestrates the execution order automatically +- Child services do NOT call other lifecycle hooks explicitly +- Side-effects that modify OTHER documents belong in `afterX` hooks or in event listeners + +### 2. Service-to-Service Communication via Domain Events + +**Architecture Pattern:** + +``` +ServiceA (modifies its own documents) + ↓ emits domain event +EventBus + ↓ delivers to listeners +ServiceB (modifies its own documents in response) +``` + +**Each service:** +- ✅ **Owns its complete document** (both `stix` and `workspace` fields) +- ✅ **Only modifies documents of its own type** +- ✅ **Emits domain events** when changes affect other services +- ✅ **Listens to domain events** from other services and responds + +**No Generic Manager Services:** +- ❌ No `EmbeddedRelationshipsManager` - services handle their own relationships +- ❌ No `ExternalReferencesManager` - services handle their own references +- ✅ Services communicate directly via domain-specific events + +### 3. Separation of Concerns + +Each STIX document has two top-level keys: +- `stix` - STIX 2.1 specification fields +- `workspace` - ATT&CK Workbench metadata + +**Ownership Model:** + +| Component | Responsibility | Authority | +|-----------|---------------|-----------| +| **SDO Services** (e.g., DetectionStrategiesService, AnalyticsService) | Manage both `stix` and `workspace` fields for their own document type | Full authority over own document properties | +| **Cross-Service Communication** | Via domain events on EventBus | Services request actions, don't directly modify other documents | + +**Rationale:** + +- **SDO services own their complete document** - both `stix` and `workspace` fields +- **Cross-document coordination** happens via events, not direct method calls +- **Each service is responsible** for maintaining its own metadata, including `workspace.embedded_relationships` +- **Keep it simple** - direct service-to-service communication is easier to trace than generic managers + +### 4. Event Bus Messaging + +**Event Naming Convention:** + +``` +::[.] +``` + +Where: +- `subject` = The entity type (lowercase STIX type, hyphenated for custom types) +- `action` = Past tense verb describing what happened +- `detail` = Optional qualifier for specialized events + +**Examples:** +- `x-mitre-detection-strategy::created` - Base CRUD event +- `x-mitre-detection-strategy::analytics-referenced` - Domain event +- `x-mitre-detection-strategy::analytics-removed` - Domain event +- `x-mitre-analytic::parent-changed` - Domain event + +**Event Payload Structure:** + +```javascript +{ + // Core identifiers + stixId: 'x-mitre-detection-strategy--...', + stixModified: '2024-01-15T10:30:00.000Z', + + // The affected document (plain object) + document: { ... }, + + // Domain-specific context + analyticIds: [...], // For analytics-referenced events + + // Additional context + options: { ... } +} +``` + +## Event Catalog + +### Core CRUD Events (Emitted by BaseService) + +| Event | When Emitted | Payload | Use Cases | +|-------|--------------|---------|-----------| +| `{type}::created` | After `afterCreate` hook | `{ stixId, document, type, options }` | Audit logging, notifications | +| `{type}::updated` | After `afterUpdate` hook | `{ stixId, stixModified, document, previousDocument, type }` | Track changes, propagate updates | +| `{type}::deleted` | After `afterDelete` hook | `{ stixId, document, options }` | Cleanup, cascade deletes | + +Where `{type}` is the STIX type (e.g., `attack-pattern`, `x-mitre-analytic`, `x-mitre-detection-strategy`). + +### Domain Events (Emitted by SDO Services) + +| Event | Emitted By | When | Payload | Listeners | +|-------|------------|------|---------|-----------| +| `x-mitre-detection-strategy::analytics-referenced` | DetectionStrategiesService | When detection strategy references analytics (create/update) | `{ detectionStrategyId, detectionStrategy, analyticIds }` | AnalyticsService | +| `x-mitre-detection-strategy::analytics-removed` | DetectionStrategiesService | When analytics removed from detection strategy | `{ detectionStrategyId, analyticIds }` | AnalyticsService | +| `x-mitre-analytic::parent-changed` | AnalyticsService | When analytic's parent detection strategy changes | `{ analyticId, oldParentId, newParentId, analytic }` | (Future: for cascading updates) | + +## Workflow Examples + +### Workflow 1: Create Detection Strategy with Analytics + +**User Action:** `POST /api/detection-strategies` with `x_mitre_analytic_refs: ['x-mitre-analytic--123']` + +**Execution Flow:** + +1. **DetectionStrategiesService.beforeCreate(data, options)** + - Initialize `data.workspace.embedded_relationships = []` + - For each analytic ref, build outbound embedded_relationship: + ```javascript + { + stix_id: 'x-mitre-analytic--123', + attack_id: 'DA-0001', // fetched from analytic + direction: 'outbound' + } + ``` + - Add to `data.workspace.embedded_relationships` + +2. **BaseService.create()** - Persist document to database + +3. **DetectionStrategiesService.afterCreate(document, options)** + - Emit domain event: `x-mitre-detection-strategy::analytics-referenced` + ```javascript + { + detectionStrategyId: 'x-mitre-detection-strategy--abc', + detectionStrategy: { ... }, + analyticIds: ['x-mitre-analytic--123'] + } + ``` + +4. **BaseService.emitCreatedEvent()** - Emit `x-mitre-detection-strategy::created` + +5. **AnalyticsService** listener receives `analytics-referenced` event + - For each analyticId: + - Fetch the analytic document + - Add inbound embedded_relationship: + ```javascript + { + stix_id: 'x-mitre-detection-strategy--abc', + attack_id: 'DS0001', + direction: 'inbound' + } + ``` + - Update analytic's `external_references` with URL: `https://attack.mitre.org/detectionstrategies/DS0001#DA-0001` + - Save the analytic + +### Workflow 2: Update Detection Strategy - Add Analytic + +**User Action:** `PUT /api/detection-strategies/{id}/{modified}` +- Change `x_mitre_analytic_refs` from `[]` to `['x-mitre-analytic--123']` + +**Execution Flow:** + +1. **DetectionStrategiesService.beforeUpdate(stixId, stixModified, data, existingDocument)** + - Detect change: `oldRefs = []`, `newRefs = ['x-mitre-analytic--123']` + - Store: `this._addedAnalyticRefs = ['x-mitre-analytic--123']` + - Rebuild outbound embedded_relationships for new refs + - Update `data.workspace.embedded_relationships` + +2. **BaseService.updateFull()** - Persist document to database + +3. **DetectionStrategiesService.afterUpdate(updatedDocument, previousDocument)** + - If `_addedAnalyticRefs` not empty: + - Emit `x-mitre-detection-strategy::analytics-referenced` + - Clean up: `delete this._addedAnalyticRefs` + +4. **BaseService.emitUpdatedEvent()** - Emit `x-mitre-detection-strategy::updated` + +5. **AnalyticsService** listener receives event and updates analytics + +### Workflow 3: Update Detection Strategy - Remove Analytic + +**User Action:** `PUT /api/detection-strategies/{id}/{modified}` +- Change `x_mitre_analytic_refs` from `['x-mitre-analytic--123']` to `[]` + +**Execution Flow:** + +1. **DetectionStrategiesService.beforeUpdate(...)** + - Detect change: `removedRefs = ['x-mitre-analytic--123']` + - Store: `this._removedAnalyticRefs = ['x-mitre-analytic--123']` + - Rebuild outbound embedded_relationships (now empty) + +2. **BaseService.updateFull()** - Persist document + +3. **DetectionStrategiesService.afterUpdate(...)** + - If `_removedAnalyticRefs` not empty: + - Emit `x-mitre-detection-strategy::analytics-removed` + ```javascript + { + detectionStrategyId: 'x-mitre-detection-strategy--abc', + analyticIds: ['x-mitre-analytic--123'] + } + ``` + - Clean up: `delete this._removedAnalyticRefs` + +4. **AnalyticsService** listener receives event + - For each analyticId: + - Fetch the analytic + - Remove inbound embedded_relationship with `stix_id: 'x-mitre-detection-strategy--abc'` + - Remove ATT&CK external reference (no parent) + - Save the analytic + +## Implementation Guidelines + +### For SDO Services + +**DO:** +- Override `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate` as needed +- Manage both `stix` and `workspace` fields for your own document type +- Emit domain events when changes affect other services +- Listen to domain events from other services +- Only modify documents of your own type + +**DON'T:** +- Override the main CRUD methods (`create`, `updateFull`, `delete`) +- Directly modify documents managed by other services +- Access repositories for other document types just to read - use events +- Implement cross-document logic without events + +**Example Service Structure:** + +```javascript +class DetectionStrategiesService extends BaseService { + // Build outbound relationships before save + async beforeCreate(data, options) { + // Modify data.workspace.embedded_relationships + } + + // Emit domain event after save + async afterCreate(document, options) { + if (document.stix.x_mitre_analytic_refs?.length > 0) { + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategyId: document.stix.id, + detectionStrategy: document.toObject(), + analyticIds: document.stix.x_mitre_analytic_refs + }); + } + } + + // Similar pattern for beforeUpdate/afterUpdate +} +``` + +### For Event Listeners + +**DO:** +- Initialize listeners in a static `initializeEventListeners()` method +- Handle events asynchronously +- Modify only documents of your own type +- Log errors and continue (don't throw) +- Emit follow-up events if needed + +**DON'T:** +- Emit CRUD events (those come from BaseService) +- Assume event ordering +- Perform blocking operations +- Throw errors that would break the event chain + +**Example Event Listener:** + +```javascript +class AnalyticsService extends BaseService { + static initializeEventListeners() { + const EventBus = require('../lib/event-bus'); + + EventBus.on( + 'x-mitre-detection-strategy::analytics-referenced', + this.handleAnalyticsReferenced.bind(this) + ); + + EventBus.on( + 'x-mitre-detection-strategy::analytics-removed', + this.handleAnalyticsRemoved.bind(this) + ); + + logger.info('AnalyticsService: Event listeners initialized'); + } + + static async handleAnalyticsReferenced(payload) { + const { detectionStrategy, analyticIds } = payload; + + for (const analyticId of analyticIds) { + try { + const analytic = await analyticsRepository.retrieveOneLatestByStixId(analyticId); + + // Add inbound relationship + if (!analytic.workspace.embedded_relationships) { + analytic.workspace.embedded_relationships = []; + } + analytic.workspace.embedded_relationships.push({ + stix_id: detectionStrategy.stix.id, + attack_id: detectionStrategy.workspace?.attack_id, + direction: 'inbound' + }); + + // Update external_references + // ... rebuild with new URL ... + + await analyticsRepository.saveDocument(analytic); + } catch (error) { + logger.error(`Error handling analytics-referenced for ${analyticId}:`, error); + // Continue processing other analytics + } + } + } +} +``` + +### Event Bus Best Practices + +**DO:** +- Use descriptive domain event names +- Include all relevant context in event payloads +- Use `await` for event emissions to ensure completion before response +- Log events at appropriate levels +- Handle Promise rejections in event handlers + +**DON'T:** +- Emit events from within event handlers (can cause cycles) +- Include sensitive data in event payloads +- Rely on handler execution order +- Use events for synchronous validation (use lifecycle hooks) + +## EventBus Implementation + +Our EventBus extends Node.js's native `EventEmitter` with additional features: + +1. **Built on Node.js Standard Library** - Leverages `events.EventEmitter` for reliability +2. **Async/Await Support** - Overrides `emit()` to handle async listeners with `Promise.allSettled` +3. **Logging & Debugging** - Logs all event registrations and emissions +4. **Event Audit Trail** - Maintains circular buffer of recent events for debugging +5. **Singleton Pattern** - Single shared bus instance across the application +6. **Increased Max Listeners** - Set to 50 to accommodate multiple services subscribing to common events + +The implementation is minimal - we only add what's necessary beyond the standard EventEmitter. + +## Architecture Benefits + +### 1. **Traceability** +- Domain event names clearly show what's happening in the business logic +- Logs show: "DetectionStrategiesService referenced analytics" → "AnalyticsService handling reference" +- Easy to follow the flow through the system + +### 2. **Ownership & Responsibility** +- Each service owns its complete document (stix + workspace) +- Clear boundaries: DetectionStrategiesService modifies detection strategies, AnalyticsService modifies analytics +- No shared generic services that modify multiple document types + +### 3. **Simplicity** +- Direct service-to-service communication via events +- No intermediate manager/coordinator services +- Fewer moving parts to understand + +### 4. **Maintainability** +- Changes to relationship logic are localized in the relevant services +- Event names document the domain interactions +- Easy to add new relationship types without infrastructure changes + +### 5. **Flexibility** +- Each service can implement domain-specific logic +- Validation rules specific to each document type +- No need to fit into generic patterns + +## Design Decisions + +### 1. **Direct Service-to-Service Communication** + - ✅ DetectionStrategiesService emits `analytics-referenced` event + - ✅ AnalyticsService listens and responds + - ❌ No generic `EmbeddedRelationshipsManager` intermediary + - **Rationale:** Simpler, more traceable, respects service ownership + +### 2. **Domain Events over Generic Events** + - ✅ `x-mitre-detection-strategy::analytics-referenced` (clear business meaning) + - ❌ `embedded-relationships::add-requested` (generic infrastructure) + - **Rationale:** Domain events tell the story of what happened in business terms + +### 3. **Services Own Complete Documents** + - ✅ Each service manages both `stix` and `workspace` fields + - ✅ Services only modify their own document types + - ❌ No shared services that modify workspace across document types + - **Rationale:** Clear ownership, single source of truth per document type + +### 4. **YAGNI - Only Build What's Needed** + - We have 1-2 embedded relationship use cases + - Generic infrastructure would be over-engineering + - If we need more patterns later, we can extract commonalities then + - **Rationale:** Optimize for the current reality, not hypothetical future + +### 5. **Request/Response Blocking** + - Services `await` event emissions + - All event handlers complete before HTTP response + - Ensures data consistency from user's perspective + - **Rationale:** REST API semantics - operations complete before response + +## Migration Status + +### Completed ✅ +- EventBus implementation (based on Node.js EventEmitter) +- Lifecycle hooks in BaseService (beforeX, afterX patterns) +- Event constants enumeration +- Architecture documentation + +### In Progress 🚧 +- Refactoring DetectionStrategiesService to emit domain events +- Implementing AnalyticsService event listeners + +### Remaining +- End-to-end testing +- Remove temporary test listeners +- Update other services if they need similar patterns diff --git a/IMPLEMENTATION_APPROACH.md b/IMPLEMENTATION_APPROACH.md new file mode 100644 index 00000000..7c7530d7 --- /dev/null +++ b/IMPLEMENTATION_APPROACH.md @@ -0,0 +1,283 @@ +# Implementation Approach: Service-to-Service Event-Driven Architecture + +## Overview + +This document describes an implementation approach for managing cross-document relationships using an event-driven architecture with direct service-to-service communication. + +## Architecture: Direction Service-to-Service Communication + +### Why This Approach? + +1. **Simpler** - No intermediate manager services +2. **More Traceable** - Domain event names clearly show business logic +3. **Better Ownership** - Each service owns its complete document +4. **YAGNI Principle** - Don't build generic infrastructure for 1-2 use cases +5. **Easier to Debug** - Direct communication path is clearer + +## Pattern + +``` +ServiceA (owns Document Type A) + │ + ├─ beforeCreate: Modify own document before save + ├─ create: [BaseService saves to database] + ├─ afterCreate: Emit domain event about what changed + │ + ↓ EventBus + │ +ServiceB (owns Document Type B) + │ + ├─ Event Listener: Receives domain event + └─ Response: Modifies own documents accordingly +``` + +## Example: Detection Strategies ↔ Analytics + +### Scenario + +When a detection strategy references analytics via `x_mitre_analytic_refs`: +- Detection strategy needs **outbound** embedded_relationships +- Analytics need **inbound** embedded_relationships +- Analytics' `external_references` need URLs pointing to parent detection strategy + +### Implementation + +```javascript +// ============================================================================ +// DetectionStrategiesService +// ============================================================================ + +class DetectionStrategiesService extends BaseService { + /** + * beforeCreate: Build outbound relationships on detection strategy + */ + async beforeCreate(data, options) { + data.workspace.embedded_relationships = []; + + const analyticRefs = data.stix?.x_mitre_analytic_refs || []; + + for (const analyticId of analyticRefs) { + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: analytic?.workspace?.attack_id, + direction: 'outbound' + }); + } + } + + /** + * afterCreate: Emit domain event to notify AnalyticsService + */ + async afterCreate(document, options) { + const analyticRefs = document.stix?.x_mitre_analytic_refs || []; + + if (analyticRefs.length > 0) { + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategyId: document.stix.id, + detectionStrategy: document.toObject(), + analyticIds: analyticRefs + }); + } + } + + /** + * beforeUpdate: Detect changes and rebuild outbound relationships + */ + async beforeUpdate(stixId, stixModified, data, existingDocument) { + const oldRefs = existingDocument.stix?.x_mitre_analytic_refs || []; + const newRefs = data.stix?.x_mitre_analytic_refs || []; + + // Store for afterUpdate + this._addedRefs = newRefs.filter(ref => !oldRefs.includes(ref)); + this._removedRefs = oldRefs.filter(ref => !newRefs.includes(ref)); + + // Rebuild outbound relationships + // ... (same logic as beforeCreate) + } + + /** + * afterUpdate: Emit events for added/removed analytics + */ + async afterUpdate(updatedDocument, previousDocument) { + if (this._addedRefs?.length > 0) { + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategyId: updatedDocument.stix.id, + detectionStrategy: updatedDocument.toObject(), + analyticIds: this._addedRefs + }); + } + + if (this._removedRefs?.length > 0) { + await EventBus.emit('x-mitre-detection-strategy::analytics-removed', { + detectionStrategyId: updatedDocument.stix.id, + analyticIds: this._removedRefs + }); + } + + delete this._addedRefs; + delete this._removedRefs; + } +} + +// ============================================================================ +// AnalyticsService +// ============================================================================ + +class AnalyticsService extends BaseService { + /** + * Initialize event listeners on app startup + */ + static initialize() { + EventBus.on( + 'x-mitre-detection-strategy::analytics-referenced', + this.handleAnalyticsReferenced.bind(this) + ); + + EventBus.on( + 'x-mitre-detection-strategy::analytics-removed', + this.handleAnalyticsRemoved.bind(this) + ); + } + + /** + * Handle analytics being referenced by a detection strategy + */ + static async handleAnalyticsReferenced(payload) { + const { detectionStrategy, analyticIds } = payload; + + for (const analyticId of analyticIds) { + const analytic = await analyticsRepository.retrieveOneLatestByStixId(analyticId); + + // Add inbound embedded_relationship + if (!analytic.workspace.embedded_relationships) { + analytic.workspace.embedded_relationships = []; + } + + analytic.workspace.embedded_relationships.push({ + stix_id: detectionStrategy.stix.id, + attack_id: detectionStrategy.workspace?.attack_id, + direction: 'inbound' + }); + + // Update external_references with URL to parent + const attackRef = createAttackExternalReference(analytic.toObject()); + if (attackRef) { + analytic.stix.external_references = + removeAttackExternalReferences(analytic.stix.external_references); + analytic.stix.external_references.unshift(attackRef); + } + + await analyticsRepository.saveDocument(analytic); + } + } + + /** + * Handle analytics being removed from a detection strategy + */ + static async handleAnalyticsRemoved(payload) { + const { detectionStrategyId, analyticIds } = payload; + + for (const analyticId of analyticIds) { + const analytic = await analyticsRepository.retrieveOneLatestByStixId(analyticId); + + // Remove inbound embedded_relationship + analytic.workspace.embedded_relationships = + analytic.workspace.embedded_relationships.filter( + rel => !(rel.stix_id === detectionStrategyId && rel.direction === 'inbound') + ); + + // Remove external_reference (no parent anymore) + analytic.stix.external_references = + removeAttackExternalReferences(analytic.stix.external_references); + + await analyticsRepository.saveDocument(analytic); + } + } +} +``` + +## Key Points + +### 1. **Each Service Owns Its Documents** +- DetectionStrategiesService modifies `detection-strategy.workspace.embedded_relationships` +- AnalyticsService modifies `analytic.workspace.embedded_relationships` +- AnalyticsService modifies `analytic.stix.external_references` + +### 2. **Communication via Domain Events** +- `x-mitre-detection-strategy::analytics-referenced` - Clear what happened +- `x-mitre-detection-strategy::analytics-removed` - Clear what happened +- NOT generic like `embedded-relationships::add-requested` + +### 3. **Lifecycle Hooks for Timing** +- `beforeCreate/beforeUpdate` - Modify own document **before** save +- `afterCreate/afterUpdate` - Emit events **after** save +- Event listeners - Modify own documents **in response** to other services + +### 4. **Initialization** +- Services with event listeners must call `initialize()` on app startup +- In `app/index.js`: + ```javascript + const AnalyticsService = require('./services/analytics-service'); + AnalyticsService.initialize(); + ``` + +## Benefits + +### ✅ Traceability +Logs show clear business flow: +``` +DetectionStrategiesService: Emitting 'x-mitre-detection-strategy::analytics-referenced' +AnalyticsService: Handling analytics being referenced +AnalyticsService: Added inbound relationship to analytic x-mitre-analytic--123 +AnalyticsService: Updated external_references URL +``` + +### ✅ Simplicity +- Two services communicating directly +- No intermediate manager/coordinator +- Easy to understand the flow + +### ✅ Ownership +- DetectionStrategiesService is responsible for detection strategies +- AnalyticsService is responsible for analytics +- Clear boundaries + +### ✅ Flexibility +- Each service can implement domain-specific logic +- No need to fit into generic patterns +- Easy to add validation specific to each type + +### ✅ Testability +- Mock EventBus +- Test DetectionStrategiesService emits correct events +- Test AnalyticsService handles events correctly +- Clear test boundaries + +## Comparison with Generic Manager Approach + +| Aspect | Direct Communication ✅ | Generic Manager ❌ | +|--------|------------------------|-------------------| +| **Event Names** | `detection-strategy::analytics-referenced` | `embedded-relationships::add-requested` | +| **Traceability** | Clear business domain flow | Generic infrastructure flow | +| **Ownership** | Services own their documents | Manager modifies multiple types | +| **Complexity** | 2 services | 3 services (+ manager) | +| **Reusability** | Each pair custom | Generic for all | +| **When to Use** | Known use cases (1-2) | Many similar patterns (5+) | + +## Current Status + +- ✅ EventBus implemented +- ✅ Lifecycle hooks in BaseService +- ✅ Architecture documented +- 🚧 Refactoring DetectionStrategiesService to emit domain events +- 🚧 Implementing AnalyticsService event listeners +- ⏳ Testing end-to-end + +## Next Steps + +1. Remove `EmbeddedRelationshipsService` (no longer needed) +2. Refactor `DetectionStrategiesService` to use lifecycle hooks + domain events +3. Implement `AnalyticsService.initialize()` and event handlers +4. Test end-to-end workflows +5. Clean up temporary test listeners diff --git a/LIFECYCLE_HOOKS_GUIDE.md b/LIFECYCLE_HOOKS_GUIDE.md new file mode 100644 index 00000000..b3d6239b --- /dev/null +++ b/LIFECYCLE_HOOKS_GUIDE.md @@ -0,0 +1,396 @@ +# Lifecycle Hooks Pattern Guide + +## Overview + +The BaseService lifecycle hooks pattern provides a structured way for child services to customize CRUD operations without overriding entire methods. This guide clarifies what the BaseService handles vs what child services should override. + +## The Three-Stage Lifecycle + +Every CRUD operation follows this pattern: + +``` +beforeX → X → afterX → emitXEvent +``` + +## What BaseService Handles (The "X" Stage) + +### During `create()`: + +**BaseService ALWAYS handles:** + +1. **Type validation** - Ensures `data.stix.type` matches service type +2. **ATT&CK ID generation** - Generates and assigns `workspace.attack_id` (if applicable) +3. **ATT&CK ID immutability checks** - Prevents users from manually setting ATT&CK IDs +4. **External references validation** - Ensures users don't manually set ATT&CK external references +5. **External reference generation** - Creates and adds ATT&CK external reference +6. **ATT&CK spec version** - Sets `x_mitre_attack_spec_version` +7. **Workflow metadata** - Records `created_by_user_account` +8. **Default marking definitions** - Applies default TLP markings +9. **Organization identity** - Sets `created_by_ref` and `x_mitre_modified_by_ref` +10. **STIX ID generation** - Generates UUID-based STIX ID if not provided +11. **Database persistence** - Calls `repository.save(data)` + +### During `updateFull()`: + +**BaseService ALWAYS handles:** + +1. **Fetch existing document** - Retrieves document by version +2. **ATT&CK ID immutability checks** - Ensures `workspace.attack_id` hasn't changed +3. **External references validation** - Validates client-provided ATT&CK references match expected values +4. **External reference repair** - Adds missing URLs or entire references if needed +5. **Database persistence** - Calls `repository.updateAndSave()` + +### During `delete()`: + +**BaseService ALWAYS handles:** + +1. **Fetch and delete** - Retrieves and removes document +2. **Database persistence** - Calls `repository.delete()` + +## What Child Services Should Override + +### Use `beforeX` hooks when you need to: + +#### `beforeCreate(data, options)` + +**MODIFY DATA before it's saved:** + +- ✅ Set default values for STIX properties +- ✅ Validate domain-specific rules +- ✅ Compute derived properties that go INTO the document being created +- ✅ Build `workspace.embedded_relationships` (outbound) +- ✅ Transform user input into canonical format + +**Examples:** +```javascript +async beforeCreate(data, options) { + // Example 1: Set defaults + if (data.stix.type === 'malware' && typeof data.stix.is_family !== 'boolean') { + data.stix.is_family = true; + } + + // Example 2: Build embedded relationships + const analyticRefs = data.stix?.x_mitre_analytic_refs || []; + if (analyticRefs.length > 0) { + const embeddedRels = await EmbeddedRelationshipsManager.buildOutboundRelationships( + analyticRefs, + (id) => this.repository.retrieveLatestByStixId(id) + ); + data.workspace.embedded_relationships.push(...embeddedRels); + } + + // Example 3: Validate domain rules + if (data.stix.x_mitre_is_subtechnique && !options.parentTechniqueId) { + throw new Error('Subtechniques require parentTechniqueId'); + } +} +``` + +**DO NOT:** +- ❌ Modify OTHER documents (use `afterX` or event listeners) +- ❌ Call `super.beforeCreate()` (it's a no-op) +- ❌ Emit events (BaseService handles this) + +#### `beforeUpdate(stixId, stixModified, data, existingDocument)` + +**MODIFY DATA before it's saved:** + +- ✅ Validate changes are allowed +- ✅ Compare old vs new values to detect changes +- ✅ Update `workspace.embedded_relationships` based on changes +- ✅ Store change detection results in instance variables for use in `afterUpdate` + +**Examples:** +```javascript +async beforeUpdate(stixId, stixModified, data, existingDocument) { + // Example 1: Detect changes + const oldRefs = existingDocument.stix.x_mitre_analytic_refs || []; + const newRefs = data.stix?.x_mitre_analytic_refs || []; + + this._addedRefs = newRefs.filter(ref => !oldRefs.includes(ref)); + this._removedRefs = oldRefs.filter(ref => !newRefs.includes(ref)); + + // Example 2: Update embedded relationships + const nonAnalyticRels = (data.workspace.embedded_relationships || []).filter( + rel => !rel.stix_id?.startsWith('x-mitre-analytic--') + ); + const analyticRels = await EmbeddedRelationshipsManager.buildOutboundRelationships( + newRefs, + (id) => this.repository.retrieveLatestByStixId(id) + ); + data.workspace.embedded_relationships = [...nonAnalyticRels, ...analyticRels]; +} +``` + +**DO NOT:** +- ❌ Modify OTHER documents +- ❌ Perform async operations that modify database (save those for `afterUpdate`) + +### Use `afterX` hooks when you need to: + +#### `afterCreate(document, options)` + +**SIDE-EFFECTS after document is saved:** + +- ✅ Update OTHER documents (e.g., add inbound relationships) +- ✅ Trigger background jobs +- ✅ Send notifications +- ✅ Update caches +- ✅ Perform cleanup + +**Examples:** +```javascript +async afterCreate(document, options) { + // Example: Update related documents + const analyticRefs = document.stix?.x_mitre_analytic_refs || []; + if (analyticRefs.length > 0) { + // Add inbound relationships to analytics + for (const analyticId of analyticRefs) { + await EmbeddedRelationshipsManager.addInboundRelationship( + analyticId, + document, + (id) => analyticsRepository.retrieveOneLatestByStixId(id), + (doc) => analyticsRepository.saveDocument(doc) + ); + } + } +} +``` + +**DO NOT:** +- ❌ Modify the `document` parameter (it's already saved) +- ❌ Return a value (return value is ignored) +- ❌ Throw errors unless you want to fail the entire operation + +#### `afterUpdate(updatedDocument, previousDocument)` + +**SIDE-EFFECTS after document is saved:** + +- ✅ Update OTHER documents based on what changed +- ✅ Propagate changes to related objects +- ✅ Trigger background jobs +- ✅ Update derived properties in other documents + +**Examples:** +```javascript +async afterUpdate(updatedDocument, previousDocument) { + // Use instance variables from beforeUpdate + if (this._addedRefs?.length > 0) { + for (const analyticId of this._addedRefs) { + await EmbeddedRelationshipsManager.addInboundRelationship( + analyticId, + updatedDocument, + (id) => analyticsRepository.retrieveOneLatestByStixId(id), + (doc) => analyticsRepository.saveDocument(doc) + ); + } + } + + if (this._removedRefs?.length > 0) { + for (const analyticId of this._removedRefs) { + await EmbeddedRelationshipsManager.removeInboundRelationship( + analyticId, + updatedDocument.stix.id, + (id) => analyticsRepository.retrieveOneLatestByStixId(id), + (doc) => analyticsRepository.saveDocument(doc) + ); + } + } + + // Clean up instance variables + delete this._addedRefs; + delete this._removedRefs; +} +``` + +### Use Event Listeners when you need to: + +**REACT to changes in OTHER documents:** + +- ✅ Update derived properties when dependencies change +- ✅ Maintain consistency across document boundaries +- ✅ Decouple cross-service dependencies + +**Examples:** +```javascript +// In AnalyticsService initialization +class AnalyticsService extends BaseService { + static initialize() { + const EventBus = require('../lib/event-bus'); + const EventConstants = require('../lib/event-constants'); + + EventBus.on( + EventConstants.EMBEDDED_RELATIONSHIP_ADDED, + this.handleEmbeddedRelationshipAdded.bind(this) + ); + + EventBus.on( + EventConstants.EMBEDDED_RELATIONSHIP_REMOVED, + this.handleEmbeddedRelationshipRemoved.bind(this) + ); + } + + static async handleEmbeddedRelationshipAdded(payload) { + const { targetId, target } = payload; + + // Only handle analytics + if (!targetId.startsWith('x-mitre-analytic--')) { + return; + } + + // Rebuild external_references with updated URL + // ... + } +} +``` + +## Services That Should Be Refactored + +Based on analysis of existing code, here are services that currently override `create()` or `updateFull()` and should be evaluated for migration to lifecycle hooks: + +### High Priority (Embedded Relationships) + +1. **DetectionStrategiesService** ⚠️ ALREADY PARTIALLY MIGRATED + - Currently: Overrides `create()` and `updateFull()` + - Should: Use `beforeCreate/afterCreate` and `beforeUpdate/afterUpdate` + - Reason: Manages embedded relationships with analytics + +2. **AnalyticsService** ⚠️ ALREADY PARTIALLY MIGRATED + - Currently: Overrides `updateFull()` + - Should: Use `beforeUpdate` + event listeners for relationship changes + - Reason: External references depend on parent detection strategy + +### Medium Priority (Domain-Specific Defaults) + +3. **SoftwareService** + - Currently: Overrides `create()` to set `is_family` defaults for malware vs tools + - Should: Use `beforeCreate()` to set defaults + - Pattern: + ```javascript + async beforeCreate(data, options) { + if (data.stix.type === 'malware' && typeof data.stix.is_family !== 'boolean') { + data.stix.is_family = true; + } + if (data.stix.type === 'tool' && data.stix.is_family !== undefined) { + throw new PropertyNotAllowedError(); + } + } + ``` + +4. **CollectionsService** + - Currently: Overrides `create()` and `updateFull()` for custom logic + - Should: Analyze and potentially use lifecycle hooks + - Needs investigation: What custom logic does it have? + +5. **CollectionIndexesService** + - Currently: Overrides methods for indexing logic + - Should: Investigate if this can use `afterCreate/afterUpdate` + events + +### Low Priority (Non-SDO Services) + +6. **IdentitiesService** - System object, may need special handling +7. **MarkingDefinitionsService** - System object, may need special handling +8. **ReferencesService** - Not a STIX object, different pattern +9. **TeamsService** - Not a STIX object, different pattern +10. **UserAccountsService** - Not a STIX object, different pattern + +## Migration Checklist + +For each service being migrated: + +- [ ] Identify what happens BEFORE database save → Move to `beforeX` +- [ ] Identify what modifies the document being saved → Move to `beforeX` +- [ ] Identify what happens AFTER database save → Move to `afterX` +- [ ] Identify what modifies OTHER documents → Move to `afterX` or event listeners +- [ ] Remove the `async create()` or `async updateFull()` override +- [ ] Test that all functionality still works +- [ ] Verify events are emitted correctly +- [ ] Check that error handling is preserved + +## Anti-Patterns to Avoid + +### ❌ DON'T override the main CRUD method + +```javascript +// BAD +async create(data, options) { + // Custom logic + data.stix.custom_field = 'value'; + + // Call parent + const doc = await super.create(data, options); + + // More custom logic + await this.updateRelatedDocuments(doc); + + return doc; +} +``` + +### ✅ DO use lifecycle hooks + +```javascript +// GOOD +async beforeCreate(data, options) { + // Modify data before save + data.stix.custom_field = 'value'; +} + +async afterCreate(document, options) { + // Side effects after save + await this.updateRelatedDocuments(document); +} +``` + +### ❌ DON'T modify other documents in `beforeX` + +```javascript +// BAD - modifying other documents before save +async beforeCreate(data, options) { + await otherRepository.updateSomething(); // ❌ Too early! +} +``` + +### ✅ DO modify other documents in `afterX` + +```javascript +// GOOD - side effects happen after save +async afterCreate(document, options) { + await otherRepository.updateSomething(); // ✅ Safe! +} +``` + +### ❌ DON'T emit events manually + +```javascript +// BAD +async afterCreate(document, options) { + await EventBus.emit('custom-event', { ... }); + await EventBus.emit(`${this.type}.created`, { ... }); // ❌ BaseService does this! +} +``` + +### ✅ DO let BaseService emit CRUD events + +```javascript +// GOOD - BaseService automatically emits CRUD events +// Only emit specialized domain events if needed +async afterCreate(document, options) { + if (someSpecialCondition) { + await EventBus.emit(EventConstants.DETECTION_STRATEGY_ANALYTICS_CHANGED, { + stixId: document.stix.id, + addedRefs: [...], + document + }); + } +} +``` + +## Benefits of This Pattern + +1. **Separation of Concerns** - BaseService handles infrastructure, child services handle domain logic +2. **Consistency** - All services follow the same pattern +3. **Testability** - Easy to test hooks in isolation +4. **Maintainability** - Clear boundaries between responsibilities +5. **Event-Driven** - Natural integration with EventBus +6. **Debuggability** - Lifecycle stages are explicit and logged From 1bffcb4d11fb7df11685cb421118f7c816ecbc08 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:11:18 -0500 Subject: [PATCH 088/370] docs: add project doc for the new task scheduler --- TASK_SCHEDULER.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 TASK_SCHEDULER.md diff --git a/TASK_SCHEDULER.md b/TASK_SCHEDULER.md new file mode 100644 index 00000000..7a86ecf0 --- /dev/null +++ b/TASK_SCHEDULER.md @@ -0,0 +1,48 @@ +# Notes + +- Refactored the task scheduler +- Uses a new library for scheduling tasks: `node-schedule` +- Uses a new pattern for detecting tasks + - The scheduler will attempt to load any tasks defined in any JS module located in `app/scheduler/` with the suffix, `-task` (e.g., `app/scheduler/foobar-task.js` will work but `app/scheduler/foo.js` will not) + - All the scheduler does is load the task module. It is up to the module defining the task to (1) implement the task, (2) load the task with the `node-schedule` library, and (3) execute the loader in the global scope + +Example: +```javascript +/** + * Initialize and schedule this task + */ +function initializeTask() { + const cronPattern = config.scheduler.myTaskNameCron; + + logger.info(`[here-is-my-task-name] Scheduling task with cron pattern: ${cronPattern}`); + + schedule.scheduleJob(cronPattern, async () => { + try { + await MYTASKTHATSHOULDRUN(); + } catch (err) { + logger.error(`[here-is-my-task-name] Task execution failed: ${err.message}`); + } + }); + + logger.info(`[here-is-my-task-name] Task scheduled successfully`); +} + +if (config.scheduler.enableScheduler) { // <-- make sure to condition the task to only load if globally enabled! + initializeTask(); +} +``` +- The old task scheduler (formerly known as the "collection manager") is now defined in `app/scheduler/sync-collection-indexes-task.js` +- Adds a new global runtime configuration setting for toggling on/off all scheduled tasks. The environment variable is `ENABLE_SCHEDULER` and it maps to `config.scheduler.enableScheduler`. +- Adds a new CRON pattern for configuring when tasks are scheduled. + - The `SYNC_COLLECTION_INDEXES_CRON` environment variable is read at runtime to determine the periodicity that the scheduler should use for the former collection manager (now the `sync-collection-indexes-tasks`). It maps to `config.scheduler.syncCollectionIndexesCron`. + - Future tasks must follow a similar pattern: + - Add the task file + +## TODO + +- [ ] Add robust documentation to `USAGE.md` explaining how task scheduling works and how to create new tasks +- [ ] In the future we should add the ability to dynamically load tasks without having to clone the repository and modify the `app/` source code. This new design pattern makes it possible to define them elsewhere and mount them via Docker volume. +- [ ] There is another task called `check-wip-attack-ids-task.js` that should probably be deleted + - It was created with the goal of restricting ATT&CK IDs to only exist on non-WIP objects + - That conversation is sort of out of scope + - I think we're going to move away from this approach and that the task will probably be moot \ No newline at end of file From f337222539ea2bfdb73fdee222686c2ba71f9256 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:05:24 -0500 Subject: [PATCH 089/370] fix(OpenAPI): drop support for manually setting workspace.embedded_relationships --- app/api/definitions/components/workspace.yml | 26 -------------------- 1 file changed, 26 deletions(-) diff --git a/app/api/definitions/components/workspace.yml b/app/api/definitions/components/workspace.yml index 52a8a080..dc5c55d2 100644 --- a/app/api/definitions/components/workspace.yml +++ b/app/api/definitions/components/workspace.yml @@ -14,12 +14,6 @@ components: type: array items: $ref: '#/components/schemas/collection_reference' - embedded_relationships: - type: array - items: - $ref: '#/components/schemas/embedded_relationship' - description: 'References to parent objects in embedded relationships (e.g., analytics referencing detection strategies)' - collection_reference: type: object properties: @@ -31,23 +25,3 @@ components: required: - collection_ref - collection_modified - - embedded_relationship: - type: object - properties: - stix_id: - type: string - description: 'STIX ID of the related object' - example: 'x-mitre-detection-strategy--12345678-1234-1234-1234-123456789abc' - attack_id: - type: string - description: 'ATT&CK ID of the related object (optional)' - example: 'DET0001' - direction: - type: string - enum: ['inbound', 'outbound'] - description: 'Specifies whether this relationship is pointing to me or I to it' - example: 'inbound' - required: - - stix_id - - direction From a0156f1e3e19add9329ca2227f7646f89ef34c93 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:08:14 -0500 Subject: [PATCH 090/370] fix(BaseService): move beforeCreate hook execution and other small refactors - Moved the beforeCreate execution call to run right before Mongoose persists the document - This enables downstream services to hook into fields after they've been set by the BaseService - Added some debug logging - Clustered error condition handling at beginning of create method --- app/services/_base.service.js | 64 ++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/app/services/_base.service.js b/app/services/_base.service.js index 70f47d1e..fa3db47f 100644 --- a/app/services/_base.service.js +++ b/app/services/_base.service.js @@ -325,10 +325,8 @@ class BaseService extends ServiceWithHooks { options = options || {}; - // LIFECYCLE HOOK: beforeCreate - // Subclasses can prepare data before core creation logic - await this.beforeCreate(data, options); if (!options.import) { + // Extracting some fields from the payload - we will need these later const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( data.stix, ); @@ -336,6 +334,7 @@ class BaseService extends ServiceWithHooks { const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; const parentTechniqueId = options?.parentTechniqueId; + // SECTION START: CHECKING ILLEGAL OPS // Throw (reject request) if user attempts to manually define the ATT&CK ID -- this field is controlled exclusively by the backend if (attackIdInExternalReferences) { logger.warn( @@ -349,7 +348,25 @@ class BaseService extends ServiceWithHooks { throw new ImmutablePropertyError('workspace.attack_id'); } - // Generate ATT&CK ID + if (data.stix?.external_references) { + // On create, clients MUST NOT provide ATT&CK external references - the backend controls this + // Throw (reject request) if user attempts to manually set the MITRE citation in the external_references array + const mitreAttackRefInExternalReferences = + attackIdGenerator.extractAttackIdFromExternalReferences(data.stix); + if (mitreAttackRefInExternalReferences) { + logger.error( + 'User manually attempted to set the MITRE ATT&CK citation at external_references.0', + ); + throw new ImmutablePropertyError('external_references.0', { + input: mitreAttackRefInExternalReferences, + }); + } + } else { + data.stix.external_references = []; + } + // SECTION END: CHECKING ILLEGAL OPS + + // Generate and set the ATT&CK ID if (attackIdGenerator.requiresAttackId(this.type)) { // Validate subtechnique requirements if (isSubtechnique && !parentTechniqueId) { @@ -375,25 +392,7 @@ class BaseService extends ServiceWithHooks { data.workspace = data.workspace || {}; data.workspace.attack_id = attackId; - logger.debug('Generated and set ATT&CK ID:', attackId); - } - - // Handle ATT&CK external reference for CREATE operations - if (data.stix?.external_references) { - // On create, clients MUST NOT provide ATT&CK external references - the backend controls this - // Throw (reject request) if user attempts to manually set the MITRE citation in the external_references array - const mitreAttackRefInExternalReferences = - attackIdGenerator.extractAttackIdFromExternalReferences(data.stix); - if (mitreAttackRefInExternalReferences) { - logger.error( - 'User manually attempted to set the MITRE ATT&CK citation at external_references.0', - ); - throw new ImmutablePropertyError('external_references.0', { - input: mitreAttackRefInExternalReferences, - }); - } - } else { - data.stix.external_references = []; + logger.debug(`Set the ATT&CK ID: ${attackId}`); } // Generate and add the ATT&CK external reference @@ -401,7 +400,9 @@ class BaseService extends ServiceWithHooks { if (attackRef) { data.stix.external_references.unshift(attackRef); } - logger.debug(`Generated and set the MITRE ATT&CK external reference: ${attackRef}`); + logger.debug( + `Generated and set the MITRE ATT&CK external reference: ${JSON.stringify(attackRef)}`, + ); // Set the ATT&CK Spec Version data.stix.x_mitre_attack_spec_version = @@ -418,6 +419,7 @@ class BaseService extends ServiceWithHooks { // Set the default marking definitions await this.setDefaultMarkingDefinitionsForObject(data); + logger.debug(`Set the default marking definition for object`); // Get the organization identity const organizationIdentityRef = await this.retrieveOrganizationIdentityRef(); @@ -432,19 +434,33 @@ class BaseService extends ServiceWithHooks { // New version of an existing object // Only set the x_mitre_modified_by_ref property data.stix.x_mitre_modified_by_ref = organizationIdentityRef; + logger.debug( + 'Found existing object with matching STIX ID - setting x_mitre_modified_by_ref', + ); } else { // New object // Assign a new STIX id if not already provided if (!data.stix.id) { data.stix.id = `${data.stix.type}--${uuid.v4()}`; } + logger.debug(`Did not find existing object - setting STIX ID: ${data.stix.id}`); // Set the created_by_ref and x_mitre_modified_by_ref properties data.stix.created_by_ref = organizationIdentityRef; + logger.debug( + `Did not find existing object - setting created_by_ref: ${data.stix.created_by_ref}`, + ); data.stix.x_mitre_modified_by_ref = organizationIdentityRef; + logger.debug( + `Did not find existing object - setting modified_by_ref: ${data.stix.x_mitre_modified_by_ref}`, + ); } } + // LIFECYCLE HOOK: beforeCreate + // Subclasses can prepare data before core creation logic + await this.beforeCreate(data, options); + // Core creation: Save the document const createdDocument = await this.repository.save(data); From 68a7385727334830150147be4f1f9a3619f075cd Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:09:14 -0500 Subject: [PATCH 091/370] feat(exceptions): added global middleware to handle uncaught exceptions thrown by service layer --- SERVICE_EXCEPTION_MIDDLEWARE.md | 131 ++++++++++++++++++++++++++++++++ app/lib/error-handler.js | 107 ++++++++++++++++++++++++++ app/routes/index.js | 1 + 3 files changed, 239 insertions(+) create mode 100644 SERVICE_EXCEPTION_MIDDLEWARE.md diff --git a/SERVICE_EXCEPTION_MIDDLEWARE.md b/SERVICE_EXCEPTION_MIDDLEWARE.md new file mode 100644 index 00000000..b507ea51 --- /dev/null +++ b/SERVICE_EXCEPTION_MIDDLEWARE.md @@ -0,0 +1,131 @@ +# Service Exception Middleware + +The global error handler now includes middleware for catching well-defined service-layer exceptions. This eliminates the need for duplicate error handling logic in controllers. + +## How It Works + +The `serviceExceptions` middleware in [app/lib/error-handler.js](app/lib/error-handler.js) automatically catches all custom exceptions from [app/exceptions/index.js](app/exceptions/index.js) and maps them to appropriate HTTP status codes: + +- **400 Bad Request**: User input errors (MissingParameterError, BadlyFormattedParameterError, InvalidQueryStringParameterError, ImmutablePropertyError, InvalidPostOperationError, InvalidTypeError, PropertyNotAllowedError, CannotUpdateStaticObjectError, BadRequestError) +- **404 Not Found**: Resource not found errors (NotFoundError, SystemConfigurationNotFound, OrganizationIdentityNotFoundError, AnonymousUserAccountNotFoundError, DefaultMarkingDefinitionsNotFoundError) +- **409 Conflict**: Duplicate resource errors (DuplicateIdError, DuplicateEmailError, DuplicateNameError) +- **500 Internal Server Error**: Service failures (IdentityServiceError, TechniquesServiceError, TacticsServiceError, GenericServiceError, DatabaseError) +- **501 Not Implemented**: Unimplemented functionality (NotImplementedError) +- **502 Bad Gateway**: External service connection errors (HostNotFoundError, ConnectionRefusedError) +- **503 Service Unavailable**: Configuration or HTTP errors (HTTPError, OrganizationIdentityNotSetError, AnonymousUserAccountNotSetError) + +## Migration Guide + +### Before (with manual exception handling) + +```javascript +const { + DuplicateIdError, + BadlyFormattedParameterError, + InvalidQueryStringParameterError, + ImmutablePropertyError, +} = require('../exceptions'); + +exports.create = async function (req, res) { + const analyticData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + }; + + try { + const analytic = await analyticsService.create(analyticData, options); + logger.debug('Success: Created analytic with id ' + analytic.stix.id); + return res.status(201).send(analytic); + } catch (err) { + if (err instanceof ImmutablePropertyError) { + return res.status(400).send(err.message); + } + if (err instanceof DuplicateIdError) { + logger.warn('Duplicate stix.id and stix.modified'); + return res.status(409).send('Unable to create analytic. Duplicate stix.id and stix.modified properties.'); + } else { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to create analytic. Server error.'); + } + } +}; +``` + +### After (with automatic exception handling) + +```javascript +exports.create = async function (req, res, next) { + const analyticData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + }; + + try { + const analytic = await analyticsService.create(analyticData, options); + logger.debug('Success: Created analytic with id ' + analytic.stix.id); + return res.status(201).send(analytic); + } catch (err) { + // Pass the error to the next middleware - the serviceExceptions middleware will handle it + return next(err); + } +}; +``` + +**Key changes:** +1. Add `next` parameter to the controller function +2. Remove all custom exception imports (unless needed for other logic) +3. Remove all `instanceof` checks for service exceptions +4. Simply call `next(err)` to pass exceptions to the middleware + +### Even Simpler (no try-catch needed) + +For controller functions that don't need any special success handling, you can use an async handler wrapper or simply let the error propagate: + +```javascript +exports.create = async function (req, res, next) { + const analyticData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + }; + + const analytic = await analyticsService.create(analyticData, options).catch(next); + if (!analytic) return; // catch(next) already handled the error + + logger.debug('Success: Created analytic with id ' + analytic.stix.id); + return res.status(201).send(analytic); +}; +``` + +## Benefits + +1. **Consistency**: All exceptions are handled uniformly across the API +2. **Maintainability**: Exception handling logic is centralized in one place +3. **Reduced code**: Controllers are simpler and easier to read +4. **Fewer bugs**: Less chance of missing an exception case +5. **Better logging**: Consistent logging format for all exceptions + +## Custom Exception Handling + +If a controller needs custom handling for specific exceptions (e.g., different error messages or additional logic), it can still catch those exceptions before calling `next(err)`: + +```javascript +exports.specialCase = async function (req, res, next) { + try { + const result = await someService.doSomething(); + return res.status(200).send(result); + } catch (err) { + // Custom handling for a specific case + if (err instanceof DuplicateIdError && req.query.merge === 'true') { + // Do something special for merge scenarios + const merged = await someService.merge(); + return res.status(200).send(merged); + } + + // All other exceptions handled by middleware + return next(err); + } +}; +``` diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index ed8d725e..077e8efe 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -1,6 +1,36 @@ 'use strict'; const logger = require('./logger'); +const { + MissingParameterError, + BadlyFormattedParameterError, + DuplicateIdError, + DuplicateEmailError, + DuplicateNameError, + NotFoundError, + InvalidQueryStringParameterError, + CannotUpdateStaticObjectError, + IdentityServiceError, + TechniquesServiceError, + TacticsServiceError, + GenericServiceError, + DatabaseError, + BadRequestError, + HostNotFoundError, + ConnectionRefusedError, + HTTPError, + NotImplementedError, + PropertyNotAllowedError, + SystemConfigurationNotFound, + OrganizationIdentityNotSetError, + OrganizationIdentityNotFoundError, + AnonymousUserAccountNotSetError, + AnonymousUserAccountNotFoundError, + InvalidTypeError, + ImmutablePropertyError, + InvalidPostOperationError, + DefaultMarkingDefinitionsNotFoundError, +} = require('../exceptions'); exports.bodyParser = function (err, req, res, next) { if (err.name === 'SyntaxError') { @@ -21,6 +51,83 @@ exports.requestValidation = function (err, req, res, next) { } }; +exports.serviceExceptions = function (err, req, res, next) { + // Handle 400 Bad Request errors (user-related errors) + if ( + err instanceof MissingParameterError || + err instanceof BadlyFormattedParameterError || + err instanceof InvalidQueryStringParameterError || + err instanceof ImmutablePropertyError || + err instanceof InvalidPostOperationError || + err instanceof InvalidTypeError || + err instanceof PropertyNotAllowedError || + err instanceof CannotUpdateStaticObjectError || + err instanceof BadRequestError + ) { + logger.warn(`Bad request: ${err.message}`); + return res.status(400).send(err.message); + } + + // Handle 404 Not Found errors + if ( + err instanceof NotFoundError || + err instanceof SystemConfigurationNotFound || + err instanceof OrganizationIdentityNotFoundError || + err instanceof AnonymousUserAccountNotFoundError || + err instanceof DefaultMarkingDefinitionsNotFoundError + ) { + logger.warn(`Not found: ${err.message}`); + return res.status(404).send(err.message); + } + + // Handle 409 Conflict errors (duplicate resources) + if ( + err instanceof DuplicateIdError || + err instanceof DuplicateEmailError || + err instanceof DuplicateNameError + ) { + logger.warn(`Conflict: ${err.message}`); + return res.status(409).send(err.message); + } + + // Handle 500 Internal Server errors (service and system errors) + if ( + err instanceof IdentityServiceError || + err instanceof TechniquesServiceError || + err instanceof TacticsServiceError || + err instanceof GenericServiceError || + err instanceof DatabaseError + ) { + logger.error(`Service error: ${err.message}`); + return res.status(500).send(err.message); + } + + // Handle 502 Bad Gateway errors (external service errors) + if (err instanceof HostNotFoundError || err instanceof ConnectionRefusedError) { + logger.error(`Bad gateway: ${err.message}`); + return res.status(502).send(err.message); + } + + // Handle 503 Service Unavailable errors (HTTP and configuration errors) + if ( + err instanceof HTTPError || + err instanceof OrganizationIdentityNotSetError || + err instanceof AnonymousUserAccountNotSetError + ) { + logger.error(`Service unavailable: ${err.message}`); + return res.status(503).send(err.message); + } + + // Handle 501 Not Implemented errors + if (err instanceof NotImplementedError) { + logger.warn(`Not implemented: ${err.message}`); + return res.status(501).send(err.message); + } + + // Pass through to next error handler if not a recognized exception + next(err); +}; + // eslint-disable-next-line no-unused-vars exports.catchAll = function (err, req, res, next) { logger.error('catch all: ' + err); diff --git a/app/routes/index.js b/app/routes/index.js index c6180c81..703202f4 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -41,6 +41,7 @@ fs.readdirSync(path.join(__dirname, '.')).forEach(function (filename) { // Handle errors that haven't otherwise been caught router.use(errorHandler.bodyParser); router.use(errorHandler.requestValidation); +router.use(errorHandler.serviceExceptions); router.use(errorHandler.catchAll); module.exports = router; From 2c81b197dbc6d08b60e291526c564bf58b7038e3 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:09:57 -0500 Subject: [PATCH 092/370] fix(attack-id-generator): update ADM import path --- app/lib/attack-id-generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js index 37c8bb47..aca90df0 100644 --- a/app/lib/attack-id-generator.js +++ b/app/lib/attack-id-generator.js @@ -4,7 +4,7 @@ const { stixTypeToAttackIdMapping, attackIdExamples, createAttackIdSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-id'); +} = require('@mitre-attack/attack-data-model'); const { InvalidTypeError, DuplicateIdError } = require('../exceptions'); const logger = require('./logger'); From fcf4510a35fa6abbed093cd90d3c091a00db7704 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:14:00 -0500 Subject: [PATCH 093/370] refactor(analytics-controller): remap query.includeRefs + pass exceptions to exceptions middleware - The service layer no longer recognizes options.includeRefs; it expects includeEmbeddedRelationships - Maps user query param includeRefs to options.includeEmbeddedRelationships - Simplifies exception handling in create and updateFull to take advantage of the new service exception handling middleware --- app/controllers/analytics-controller.js | 43 ++++++++----------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/app/controllers/analytics-controller.js b/app/controllers/analytics-controller.js index fde0e9b0..56d8d8d2 100644 --- a/app/controllers/analytics-controller.js +++ b/app/controllers/analytics-controller.js @@ -2,12 +2,7 @@ const analyticsService = require('../services/analytics-service'); const logger = require('../lib/logger'); -const { - DuplicateIdError, - BadlyFormattedParameterError, - InvalidQueryStringParameterError, - ImmutablePropertyError, -} = require('../exceptions'); +const { BadlyFormattedParameterError, InvalidQueryStringParameterError } = require('../exceptions'); exports.retrieveAll = async function (req, res) { const options = { @@ -20,7 +15,8 @@ exports.retrieveAll = async function (req, res) { search: req.query.search, lastUpdatedBy: req.query.lastUpdatedBy, includePagination: req.query.includePagination, - includeRefs: req.query.includeRefs === 'true' || req.query.includeRefs === true, + includeEmbeddedRelationships: + req.query.includeRefs === 'true' || req.query.includeRefs === true, }; try { @@ -42,7 +38,8 @@ exports.retrieveAll = async function (req, res) { exports.retrieveById = async function (req, res) { const options = { versions: req.query.versions || 'latest', - includeRefs: req.query.includeRefs === 'true' || req.query.includeRefs === true, + includeEmbeddedRelationships: + req.query.includeRefs === 'true' || req.query.includeRefs === true, }; try { @@ -92,7 +89,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { // Get the data from the request const analyticData = req.body; const options = { @@ -100,32 +97,21 @@ exports.create = async function (req, res) { userAccountId: req.user?.userAccountId, }; - // Create the analytic try { + // Create the analytic const analytic = await analyticsService.create(analyticData, options); logger.debug('Success: Created analytic with id ' + analytic.stix.id); return res.status(201).send(analytic); } catch (err) { - if (err instanceof ImmutablePropertyError) { - return res.status(400).send(err.message); - } - if (err instanceof DuplicateIdError) { - logger.warn('Duplicate stix.id and stix.modified'); - return res - .status(409) - .send('Unable to create analytic. Duplicate stix.id and stix.modified properties.'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create analytic. Server error.'); - } + // Pass the error to the service exception middleware + return next(err); } }; -exports.updateFull = async function (req, res) { +exports.updateFull = async function (req, res, next) { // Get the data from the request const analyticData = req.body; - // Create the analytic try { const analytic = await analyticsService.updateFull( req.params.stixId, @@ -134,13 +120,12 @@ exports.updateFull = async function (req, res) { ); if (!analytic) { return res.status(404).send('Analytic not found.'); - } else { - logger.debug('Success: Updated analytic with id ' + analytic.stix.id); - return res.status(200).send(analytic); } + logger.debug('Success: Updated analytic with id ' + analytic.stix.id); + return res.status(200).send(analytic); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update analytic. Server error.'); + // Pass the error to the service exception middleware + return next(err); } }; From 11e98262f2d6af92a199676fc9f06feba666719c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:14:38 -0500 Subject: [PATCH 094/370] feat(workspace): add support for tracking name on embedded relationships --- app/models/subschemas/workspace.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/subschemas/workspace.js b/app/models/subschemas/workspace.js index ad8ef3ca..a1f2fd8d 100644 --- a/app/models/subschemas/workspace.js +++ b/app/models/subschemas/workspace.js @@ -11,6 +11,7 @@ const collectionVersionSchema = new mongoose.Schema(collectionVersion, { _id: fa const embedddedRelationship = { stix_id: { type: String, required: true }, attack_id: String, + name: String, direction: { type: String, // inbound: The embedded relationship points TO this document (I am referenced) @@ -34,7 +35,7 @@ module.exports.common = { }, attack_id: String, collections: [collectionVersionSchema], - embedded_relationships: { type: [embeddedRelationshipSchema], required: true }, + embedded_relationships: { type: [embeddedRelationshipSchema] }, }; // x-mitre-collection workspace structure From 96ee5d3e35b72aa4f14a294c12728d9ce0a4df13 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:15:13 -0500 Subject: [PATCH 095/370] refactor(BaseRepository): add error logging to save method --- app/repository/_base.repository.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index 7ce31d0b..82710a5c 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -348,6 +348,7 @@ class BaseRepository extends AbstractRepository { const document = new this.model(data); return await document.save(); } catch (err) { + logger.error(`A database error occurred: ${err.message}`); if (err.name === 'MongoServerError' && err.code === 11000) { throw new DuplicateIdError({ details: `Document with id '${data.stix.id}' already exists.`, From f5d4eccc339906f5b1d064a0ef4966c17588ef4b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:16:03 -0500 Subject: [PATCH 096/370] feat(event-constants): add new event types for handling analytics and data components --- app/lib/event-constants.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index f2ccff85..b5a25d7a 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -112,6 +112,12 @@ module.exports = Object.freeze({ // Detection Strategy - Analytics relationship DETECTION_STRATEGY_ANALYTICS_CHANGED: 'detection-strategy::analytics-changed', + DETECTION_STRATEGY_ANALYTICS_REFERENCED: 'x-mitre-detection-strategy::analytics-referenced', + DETECTION_STRATEGY_ANALYTICS_REMOVED: 'x-mitre-detection-strategy::analytics-removed', + + // Analytic - Data Components relationship + ANALYTIC_DATA_COMPONENTS_REFERENCED: 'x-mitre-analytic::data-components-referenced', + ANALYTIC_DATA_COMPONENTS_REMOVED: 'x-mitre-analytic::data-components-removed', // Technique - Sub-technique conversion TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE: 'attack-pattern::converted-to-subtechnique', From 0c6f06fbd9d6c30a4e6a86dc0084045c2016146f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:27:33 -0500 Subject: [PATCH 097/370] refactor(analytics-service): migrate to new event-driven architecture - replace updateFull override with beforeUpdate and afterUpdate lifecycle hooks - add event emitters to notify DataComponentsService when DCs are referenced - add JSDoc to all methods --- app/services/analytics-service.js | 388 ++++++++++++++++++++---------- 1 file changed, 267 insertions(+), 121 deletions(-) diff --git a/app/services/analytics-service.js b/app/services/analytics-service.js index d2492bee..b26b6f71 100644 --- a/app/services/analytics-service.js +++ b/app/services/analytics-service.js @@ -1,23 +1,33 @@ 'use strict'; const analyticsRepository = require('../repository/analytics-repository'); +const dataComponentsRepository = require('../repository/data-components-repository'); const BaseService = require('./_base.service'); const { Analytic: AnalyticType } = require('../lib/types'); -const detectionStrategiesService = require('./detection-strategies-service'); -const dataComponentsService = require('./data-components-service'); const { createAttackExternalReference, removeAttackExternalReferences, } = require('../lib/external-reference-builder'); const EventBus = require('../lib/event-bus'); const logger = require('../lib/logger'); +const { NotFoundError } = require('../exceptions'); /** * Service for managing analytics * + * Lifecycle hooks: + * - beforeCreate: Builds outbound embedded_relationships for data components and validates they exist + * - afterCreate: Emits domain event to notify DataComponentsService + * - beforeUpdate: Rebuilds outbound embedded_relationships, validates data component references, and detects changes + * - afterUpdate: Emits domain events for added/removed data components + * * Event listeners: * - x-mitre-detection-strategy::analytics-referenced - Add inbound relationships when detection strategy references analytics * - x-mitre-detection-strategy::analytics-removed - Remove inbound relationships when detection strategy removes analytics + * + * Events emitted (listened to by DataComponentsService): + * - x-mitre-analytic::data-components-referenced + * - x-mitre-analytic::data-components-removed */ class AnalyticsService extends BaseService { /** @@ -41,10 +51,19 @@ class AnalyticsService extends BaseService { /** * Handle analytics being referenced by a detection strategy * Add inbound embedded_relationship and update external_references + * + * @param {Object} payload - Event payload + * @param {Object} payload.detectionStrategy - The detection strategy document that references the analytics + * @param {string[]} payload.analyticIds - Array of analytic STIX IDs being referenced + * @returns {Promise} */ static async handleAnalyticsReferenced(payload) { const { detectionStrategy, analyticIds } = payload; + logger.info( + `Analytics Service heard event: 'x-mitre-detection-strategy::analytics-referenced' for ${detectionStrategy.stix.id}`, + ); + for (const analyticId of analyticIds) { try { const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); @@ -74,6 +93,7 @@ class AnalyticsService extends BaseService { analytic.workspace.embedded_relationships.push({ stix_id: detectionStrategy.stix.id, attack_id: detectionStrategy.workspace?.attack_id || null, + name: detectionStrategy.stix.name, direction: 'inbound', }); @@ -115,6 +135,11 @@ class AnalyticsService extends BaseService { /** * Handle analytics being removed from a detection strategy * Remove inbound embedded_relationship and update external_references + * + * @param {Object} payload - Event payload + * @param {string} payload.detectionStrategyId - STIX ID of the detection strategy + * @param {string[]} payload.analyticIds - Array of analytic STIX IDs being removed + * @returns {Promise} */ static async handleAnalyticsRemoved(payload) { const { detectionStrategyId, analyticIds } = payload; @@ -184,19 +209,174 @@ class AnalyticsService extends BaseService { } } } + /** - * Override updateFull to ensure external_references URL is updated if parent detection strategy changes - * This can happen if the analytic's embedded_relationships are modified + * Lifecycle hook: Prepare analytic data before database persistence + * - Sets analytic name to match ATT&CK ID + * - Builds outbound embedded_relationships for data component references + * - Validates that all referenced data components exist + * + * @param {Object} data - The analytic data to be created + * @param {Object} data.stix - STIX properties + * @param {Object} data.workspace - Workbench metadata + * @param {Object} options - Creation options + * @throws {NotFoundError} If a referenced data component does not exist + * @returns {Promise} */ - async updateFull(stixId, stixModified, data) { - // Get the existing document - const existingAnalytic = await this.repository.retrieveOneByVersion(stixId, stixModified); - if (!existingAnalytic) { - return null; + // eslint-disable-next-line no-unused-vars + async beforeCreate(data, options) { + // Analytic name matches its ATT&CK ID + data.stix.name = data.workspace.attack_id; + logger.debug(`Setting name to match ATT&CK ID: ${data.stix.name}`); + + // Initialize embedded_relationships if not present + if (!data.workspace) { + data.workspace = {}; + } + if (!data.workspace.embedded_relationships) { + data.workspace.embedded_relationships = []; + } + + // Build outbound embedded_relationships for data component references + // Cross-repository READS are allowed for denormalization (see CROSS_SERVICE_READS_PATTERN.md) + const dataComponentRefs = + data.stix?.x_mitre_log_source_references?.map((ref) => ref.x_mitre_data_component_ref) || []; + + if (dataComponentRefs.length > 0) { + for (const dataComponentId of dataComponentRefs) { + const dataComponent = + await dataComponentsRepository.retrieveLatestByStixId(dataComponentId); + if (!dataComponent) { + throw new NotFoundError({ + objectType: 'x-mitre-data-component', + objectId: dataComponentId, + message: `Cannot create analytic: Referenced data component ${dataComponentId} does not exist`, + }); + } + + // Add outbound embedded_relationship + data.workspace.embedded_relationships.push({ + stix_id: dataComponentId, + attack_id: dataComponent.workspace?.attack_id || null, + name: dataComponent.stix?.name || null, + direction: 'outbound', + }); + } + + logger.debug( + `Built ${dataComponentRefs.length} outbound embedded relationship(s) for analytic ${data.workspace.attack_id}`, + ); } + } + + /** + * Lifecycle hook: Handle post-creation side effects + * Emits domain event to notify DataComponentsService that data components were referenced + * + * @param {Object} createdDocument - The created analytic document + * @param {Object} _options - Creation options (unused) + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + async afterCreate(createdDocument, _options) { + // Extract data component IDs from x_mitre_log_source_references + const dataComponentRefs = + createdDocument.stix?.x_mitre_log_source_references?.map( + (ref) => ref.x_mitre_data_component_ref, + ) || []; + + if (dataComponentRefs.length > 0) { + logger.info( + `AnalyticsService: Emitting data-components-referenced event for ${dataComponentRefs.length} data component(s)`, + { stixId: createdDocument.stix.id, dataComponentIds: dataComponentRefs }, + ); + + await EventBus.emit('x-mitre-analytic::data-components-referenced', { + analyticId: createdDocument.stix.id, + analytic: createdDocument.toObject ? createdDocument.toObject() : createdDocument, + dataComponentIds: dataComponentRefs, + }); + } + } + + /** + * Lifecycle hook: Prepare analytic data before update persistence + * - Rebuilds outbound embedded_relationships for data components + * - Preserves inbound relationships from detection strategies + * - Validates that all referenced data components exist + * - Detects changes in data component references for event emission + * - Updates external_references if parent detection strategy changes + * + * @param {string} stixId - STIX ID of the analytic being updated + * @param {string} stixModified - Modified timestamp of the version being updated + * @param {Object} data - Updated analytic data + * @param {Object} existingDocument - The existing analytic document + * @throws {NotFoundError} If a referenced data component does not exist + * @returns {Promise} + */ + async beforeUpdate(stixId, stixModified, data, existingDocument) { + // Initialize embedded_relationships if not present + if (!data.workspace) { + data.workspace = {}; + } + if (!data.workspace.embedded_relationships) { + data.workspace.embedded_relationships = []; + } + + // Validate that all referenced data components exist and build outbound relationships + const newDataComponentRefs = + data.stix?.x_mitre_log_source_references?.map((ref) => ref.x_mitre_data_component_ref) || []; + + // Preserve existing non-data-component embedded relationships (e.g., inbound from detection strategies) + const existingNonDataComponentRels = (data.workspace.embedded_relationships || []).filter( + (rel) => !rel.stix_id?.startsWith('x-mitre-data-component--'), + ); + + // Build new outbound embedded relationships for data components + const dataComponentEmbeddedRels = []; + if (newDataComponentRefs.length > 0) { + for (const dataComponentId of newDataComponentRefs) { + const dataComponent = + await dataComponentsRepository.retrieveLatestByStixId(dataComponentId); + if (!dataComponent) { + throw new NotFoundError({ + objectType: 'x-mitre-data-component', + objectId: dataComponentId, + message: `Cannot update analytic: Referenced data component ${dataComponentId} does not exist`, + }); + } + + // Add outbound embedded_relationship + dataComponentEmbeddedRels.push({ + stix_id: dataComponentId, + attack_id: dataComponent.workspace?.attack_id || null, + name: dataComponent.stix?.name || null, + direction: 'outbound', + }); + } + } + + // Rebuild embedded_relationships: preserve inbound relationships, rebuild outbound data component relationships + data.workspace.embedded_relationships = [ + ...existingNonDataComponentRels, + ...dataComponentEmbeddedRels, + ]; + + // Detect changes in data component references for event emission + const oldDataComponentRefs = + existingDocument.stix?.x_mitre_log_source_references?.map( + (ref) => ref.x_mitre_data_component_ref, + ) || []; + + this._addedDataComponentRefs = newDataComponentRefs.filter( + (ref) => !oldDataComponentRefs.includes(ref), + ); + this._removedDataComponentRefs = oldDataComponentRefs.filter( + (ref) => !newDataComponentRefs.includes(ref), + ); // Check if embedded_relationships changed (specifically inbound detection strategy relationships) - const oldEmbeddedRels = existingAnalytic.workspace?.embedded_relationships || []; + const oldEmbeddedRels = existingDocument.workspace?.embedded_relationships || []; const newEmbeddedRels = data.workspace?.embedded_relationships || []; const oldParentStrategy = oldEmbeddedRels.find( @@ -227,141 +407,107 @@ class AnalyticsService extends BaseService { data.stix.external_references.unshift(attackRef); } } + } + + /** + * Lifecycle hook: Handle post-update side effects + * Emits domain events for added/removed data component references + * + * @param {Object} updatedDocument - The updated analytic document + * @param {Object} _previousDocument - The previous version of the analytic (unused) + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + async afterUpdate(updatedDocument, _previousDocument) { + const addedRefs = this._addedDataComponentRefs || []; + const removedRefs = this._removedDataComponentRefs || []; + + // Emit event for newly referenced data components + if (addedRefs.length > 0) { + logger.info( + `AnalyticsService: Emitting data-components-referenced event for ${addedRefs.length} added data component(s)`, + { stixId: updatedDocument.stix.id, dataComponentIds: addedRefs }, + ); + + await EventBus.emit('x-mitre-analytic::data-components-referenced', { + analyticId: updatedDocument.stix.id, + analytic: updatedDocument.toObject ? updatedDocument.toObject() : updatedDocument, + dataComponentIds: addedRefs, + }); + } - // Call parent updateFull to handle all standard update logic - return await super.updateFull(stixId, stixModified, data); + // Emit event for removed data components + if (removedRefs.length > 0) { + logger.info( + `AnalyticsService: Emitting data-components-removed event for ${removedRefs.length} removed data component(s)`, + { stixId: updatedDocument.stix.id, dataComponentIds: removedRefs }, + ); + + await EventBus.emit('x-mitre-analytic::data-components-removed', { + analyticId: updatedDocument.stix.id, + dataComponentIds: removedRefs, + }); + } + + // Clean up instance variables + delete this._addedDataComponentRefs; + delete this._removedDataComponentRefs; } + + /** + * Retrieve all analytics with optional filtering and pagination + * Strips embedded_relationships from response unless explicitly requested + * + * @param {Object} options - Query options + * @param {boolean} [options.includeEmbeddedRelationships=false] - Include embedded relationships in response + * @param {boolean} [options.includePagination=false] - Include pagination metadata + * @returns {Promise} Array of analytics or paginated result object + */ async retrieveAll(options) { const results = await super.retrieveAll(options); - if (options.includeRefs) { + if (!options.includeEmbeddedRelationships) { if (options.includePagination) { - await this.addRelatedObjectsToAll(results.data); + await this.stripEmbeddedRelationships(results.data); } else { - await this.addRelatedObjectsToAll(results); + await this.stripEmbeddedRelationships(results); } } return results; } + /** + * Retrieve analytics by STIX ID + * Strips embedded_relationships from response unless explicitly requested + * + * @param {string} stixId - The STIX ID of the analytic + * @param {Object} options - Query options + * @param {boolean} [options.includeEmbeddedRelationships=false] - Include embedded relationships in response + * @returns {Promise} Array of analytic versions + */ async retrieveById(stixId, options) { const results = await super.retrieveById(stixId, options); - if (options.includeRefs) { - await this.addRelatedObjectsToAll(results); + if (!options.includeEmbeddedRelationships) { + await this.stripEmbeddedRelationships(results); } return results; } - async addRelatedObjectsToAll(analytics) { + /** + * Remove embedded_relationships from analytics response + * Used to hide internal relationship metadata from API consumers + * + * @param {Array} analytics - Array of analytic documents + * @returns {Promise} + */ + async stripEmbeddedRelationships(analytics) { for (const analytic of analytics) { - await this.addRelatedObjects(analytic); + delete analytic.stix.embedded_relationships; } } - - async addRelatedObjects(analytic) { - const relatedObjects = []; - - try { - // Find detection strategies that reference this analytic - const detectionStrategies = await this.findDetectionStrategiesReferencingAnalytic( - analytic.stix.id, - ); - for (const detectionStrategy of detectionStrategies) { - relatedObjects.push( - this.formatRelatedObject(detectionStrategy, 'x-mitre-detection-strategy'), - ); - } - - // Find data components referenced by this analytic - const dataComponents = await this.findDataComponentsReferencedByAnalytic(analytic); - for (const dataComponent of dataComponents) { - relatedObjects.push(this.formatRelatedObject(dataComponent, 'x-mitre-data-component')); - } - } catch (err) { - // Log error but don't fail the main request - console.warn('Error fetching related objects for analytic:', err.message); - } - - analytic.related_to = relatedObjects; - } - - async findDetectionStrategiesReferencingAnalytic(analyticId) { - try { - // Query detection strategies where x_mitre_analytics array contains the analytic ID - const options = { - offset: 0, - limit: 0, - includeRevoked: false, - includeDeprecated: false, - includePagination: false, - }; - - // Get all detection strategies and filter in memory - // (BaseRepository doesn't have a direct way to query array contains) - const allStrategies = await detectionStrategiesService.retrieveAll(options); - - return allStrategies.filter( - (strategy) => - strategy.stix.x_mitre_analytic_refs && - strategy.stix.x_mitre_analytic_refs.includes(analyticId), - ); - } catch (err) { - console.warn('Error finding detection strategies:', err.message); - return []; - } - } - - async findDataComponentsReferencedByAnalytic(analytic) { - try { - if ( - !analytic.stix.x_mitre_log_source_references || - analytic.stix.x_mitre_log_source_references.length === 0 - ) { - return []; - } - - const dataComponentIds = analytic.stix.x_mitre_log_source_references.map( - (ref) => ref.x_mitre_data_component_ref, - ); - const dataComponents = []; - - // Fetch each data component by ID - for (const dataComponentId of dataComponentIds) { - try { - const dataComponentResults = await dataComponentsService.retrieveById(dataComponentId, { - versions: 'latest', - }); - if (dataComponentResults.length > 0) { - dataComponents.push(dataComponentResults[0]); - } - } catch (err) { - console.warn(`Error fetching data component ${dataComponentId}:`, err.message); - } - } - - return dataComponents; - } catch (err) { - console.warn('Error finding data components:', err.message); - return []; - } - } - - formatRelatedObject(obj, type) { - const attackId = - obj.stix.external_references && obj.stix.external_references.length > 0 - ? obj.stix.external_references[0].external_id - : null; - - return { - id: obj.stix.id, - name: obj.stix.name, - attack_id: attackId, - type: type, - }; - } } AnalyticsService.initializeEventListeners(); From a46a7b537ecea2ff8219eb26ac6ed76013d8f3b8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:29:15 -0500 Subject: [PATCH 098/370] refactor(data-components-service): migrate to new event-driven architecture Implement event listeners for data components being referenced and/or deleted by an analytic --- app/services/data-components-service.js | 142 +++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/app/services/data-components-service.js b/app/services/data-components-service.js index 07ee7529..9a8b5a97 100644 --- a/app/services/data-components-service.js +++ b/app/services/data-components-service.js @@ -3,7 +3,147 @@ const dataComponentsRepository = require('../repository/data-components-repository.js'); const BaseService = require('./_base.service'); const { DataComponent: DataComponentType } = require('../lib/types.js'); +const EventBus = require('../lib/event-bus'); +const logger = require('../lib/logger'); -class DataComponentsService extends BaseService {} +/** + * Service for managing data components + * + * Event listeners: + * - x-mitre-analytic::data-components-referenced - Add inbound relationships when analytic references data components + * - x-mitre-analytic::data-components-removed - Remove inbound relationships when analytic removes data components + */ +class DataComponentsService extends BaseService { + /** + * Initialize event listeners + * Called once on app startup + */ + static initializeEventListeners() { + EventBus.on( + 'x-mitre-analytic::data-components-referenced', + this.handleDataComponentsReferenced.bind(this), + ); + + EventBus.on( + 'x-mitre-analytic::data-components-removed', + this.handleDataComponentsRemoved.bind(this), + ); + + logger.info('DataComponentsService: Event listeners initialized'); + } + + /** + * Handle data components being referenced by an analytic + * Add inbound embedded_relationship + */ + static async handleDataComponentsReferenced(payload) { + const { analytic, dataComponentIds } = payload; + + logger.info( + `DataComponentsService heard event: 'x-mitre-analytic::data-components-referenced' for ${analytic.stix.id}`, + ); + + for (const dataComponentId of dataComponentIds) { + try { + const dataComponent = + await dataComponentsRepository.retrieveLatestByStixId(dataComponentId); + + if (!dataComponent) { + logger.warn( + `DataComponentsService: Could not find data component ${dataComponentId} to add inbound relationship`, + ); + continue; + } + + // Initialize embedded_relationships if needed + if (!dataComponent.workspace) { + dataComponent.workspace = {}; + } + if (!dataComponent.workspace.embedded_relationships) { + dataComponent.workspace.embedded_relationships = []; + } + + // Check if relationship already exists + const exists = dataComponent.workspace.embedded_relationships.some( + (rel) => rel.stix_id === analytic.stix.id && rel.direction === 'inbound', + ); + + if (!exists) { + // Add inbound embedded_relationship + dataComponent.workspace.embedded_relationships.push({ + stix_id: analytic.stix.id, + attack_id: analytic.workspace?.attack_id || null, + name: analytic.stix.name, + direction: 'inbound', + }); + + logger.info( + `DataComponentsService: Added inbound relationship from analytic ${analytic.stix.id} to data component ${dataComponentId}`, + ); + } + + await dataComponentsRepository.saveDocument(dataComponent); + } catch (error) { + logger.error( + `DataComponentsService: Error handling data-components-referenced for ${dataComponentId}:`, + error, + ); + // Continue processing other data components + } + } + } + + /** + * Handle data components being removed from an analytic + * Remove inbound embedded_relationship + */ + static async handleDataComponentsRemoved(payload) { + const { analyticId, dataComponentIds } = payload; + + logger.info( + `DataComponentsService heard event: 'x-mitre-analytic::data-components-removed' for ${analyticId}`, + ); + + for (const dataComponentId of dataComponentIds) { + try { + const dataComponent = + await dataComponentsRepository.retrieveLatestByStixId(dataComponentId); + + if (!dataComponent) { + logger.warn( + `DataComponentsService: Could not find data component ${dataComponentId} to remove inbound relationship`, + ); + continue; + } + + if (dataComponent.workspace?.embedded_relationships) { + // Remove inbound embedded_relationship + const initialLength = dataComponent.workspace.embedded_relationships.length; + dataComponent.workspace.embedded_relationships = + dataComponent.workspace.embedded_relationships.filter( + (rel) => !(rel.stix_id === analyticId && rel.direction === 'inbound'), + ); + + const removed = dataComponent.workspace.embedded_relationships.length < initialLength; + if (removed) { + logger.info( + `DataComponentsService: Removed inbound relationship from analytic ${analyticId} to data component ${dataComponentId}`, + ); + } + } + + await dataComponentsRepository.saveDocument(dataComponent); + } catch (error) { + logger.error( + `DataComponentsService: Error handling data-components-removed for ${dataComponentId}:`, + error, + ); + // Continue processing other data components + } + } + } +} + +DataComponentsService.initializeEventListeners(); module.exports = new DataComponentsService(DataComponentType, dataComponentsRepository); From fca26a1e8fd1c2c137ab47508712c14c4dcbafc8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:30:54 -0500 Subject: [PATCH 099/370] docs: add project documentation specifying rules for cross-service interactions --- CROSS_SERVICE_READS_PATTERN.md | 245 +++++++++++++++++++++++++++++++++ EVENT_BUS_ARCHITECTURE.md | 36 +++++ 2 files changed, 281 insertions(+) create mode 100644 CROSS_SERVICE_READS_PATTERN.md diff --git a/CROSS_SERVICE_READS_PATTERN.md b/CROSS_SERVICE_READS_PATTERN.md new file mode 100644 index 00000000..e7b256c1 --- /dev/null +++ b/CROSS_SERVICE_READS_PATTERN.md @@ -0,0 +1,245 @@ +# Cross-Service Reads in Event-Driven Architecture + +## The Dilemma + +When building embedded relationships (denormalized metadata cache), services need information from documents managed by other services. For example: + +```javascript +// DetectionStrategiesService needs analytic metadata +data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: analytic?.workspace?.attack_id, // From AnalyticsService + name: analytic.stix.name, // From AnalyticsService + direction: 'outbound', +}); +``` + +**Question:** Does fetching this metadata violate the event-driven architecture principle? + +## Answer: No - Reads Are Different From Writes + +### Core Principle Refinement + +The event-driven architecture aims to eliminate **cross-service WRITES**, not reads. + +| Operation | Allowed? | Rationale | +|-----------|----------|-----------| +| Service A reads from Service B's repository | ✅ YES | Safe, idempotent, necessary for denormalization | +| Service A writes to Service B's repository | ❌ NO | Violates ownership, creates tight coupling | +| Service A emits event → Service B writes to its own repository | ✅ YES | Event-driven pattern, maintains boundaries | + +### Why Cross-Service Reads Are Acceptable + +1. **Reads don't violate ownership** - AnalyticsService still owns analytics data; DetectionStrategiesService is just observing it + +2. **Reads don't create consistency issues** - No competing writes, no transaction boundaries to manage + +3. **Reads are necessary for denormalization** - Embedded relationships are a materialized view requiring source data + +4. **Reads don't create temporal coupling** - If analytics don't exist yet, handle gracefully with null values + +## Design Patterns + +### ✅ Pattern: Read During Denormalization + +**When to use:** Building cached metadata (embedded_relationships, computed fields) + +```javascript +class DetectionStrategiesService extends BaseService { + async beforeCreate(data) { + // ALLOWED: Reading analytics metadata to denormalize + for (const analyticId of data.stix.x_mitre_analytic_refs) { + try { + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: analytic?.workspace?.attack_id || null, + name: analytic.stix.name, + direction: 'outbound', + }); + } catch (error) { + // Handle missing analytics gracefully + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: null, + name: null, + direction: 'outbound', + }); + } + } + } + + async afterCreate(document) { + // REQUIRED: Emit event so AnalyticsService can update its own documents + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategy: document, + analyticIds: document.stix.x_mitre_analytic_refs + }); + } +} +``` + +### ✅ Pattern: Event-Driven Writes + +**When to use:** Updating documents managed by another service + +```javascript +class AnalyticsService extends BaseService { + static initializeEventListeners() { + EventBus.on( + 'x-mitre-detection-strategy::analytics-referenced', + this.handleAnalyticsReferenced.bind(this) + ); + } + + static async handleAnalyticsReferenced(payload) { + const { detectionStrategy, analyticIds } = payload; + + for (const analyticId of analyticIds) { + // CORRECT: AnalyticsService modifying its own documents + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + analytic.workspace.embedded_relationships.push({ + stix_id: detectionStrategy.stix.id, + attack_id: detectionStrategy.workspace?.attack_id, + name: detectionStrategy.stix.name, + direction: 'inbound', + }); + + await analyticsRepository.saveDocument(analytic); + } + } +} +``` + +### ❌ Anti-Pattern: Direct Cross-Service Write + +**Never do this:** + +```javascript +class DetectionStrategiesService extends BaseService { + async afterCreate(document) { + // WRONG: DetectionStrategiesService directly modifying analytics + for (const analyticId of document.stix.x_mitre_analytic_refs) { + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + analytic.workspace.embedded_relationships.push({ + stix_id: document.stix.id, + direction: 'inbound', + }); + + await analyticsRepository.saveDocument(analytic); // ❌ WRONG! + } + } +} +``` + +**Why this is wrong:** +- Violates single responsibility (DetectionStrategiesService shouldn't know how to update analytics) +- Creates tight coupling +- Makes testing harder +- Harder to trace who modified what + +## When Should You Denormalize? + +Denormalization (embedded_relationships) is appropriate when: + +1. **UI needs the data frequently** - Avoids N+1 query problems +2. **Data changes infrequently** - Analytics names don't change often +3. **Staleness is acceptable** - If analytic name changes, embedded relationship name will be stale until next update +4. **Read performance matters** - Joining at query time would be too slow + +If these don't apply, consider storing only IDs and joining at query time. + +## Alternatives Considered + +### Alternative 1: No Denormalization (Store Only IDs) + +```javascript +// Minimal approach +data.workspace.embedded_relationships.push({ + stix_id: analyticId, + direction: 'outbound', +}); + +// Fetch names at query time +const analytics = await Promise.all( + embeddedRels.map(rel => analyticsRepository.retrieveLatestByStixId(rel.stix_id)) +); +``` + +**Trade-offs:** +- ✅ No cross-service reads needed +- ✅ Always current data +- ❌ Slower API responses (N+1 queries) +- ❌ More complex query logic + +### Alternative 2: Event-Based Metadata Request (Over-Engineering) + +```javascript +// Request metadata via events +const metadata = await EventBus.emitAndWait('analytic::metadata-requested', { + analyticIds +}); +``` + +**Trade-offs:** +- ❌ Turns events into synchronous RPC (defeats purpose) +- ❌ Adds latency and complexity +- ❌ Just hiding the read behind an event facade + +**Verdict:** Not recommended - you're just wrapping a read with more abstraction + +## Recommendations + +### For DetectionStrategiesService + +1. ✅ **Keep the cross-repository read in `beforeCreate`** - This is correct and necessary +2. ✅ **Emit events in `afterCreate`** - Let AnalyticsService update its own documents +3. ✅ **Handle missing analytics gracefully** - Store null values if analytic doesn't exist + +### For Documentation + +Update [EVENT_BUS_ARCHITECTURE.md](EVENT_BUS_ARCHITECTURE.md) to clarify: + +```markdown +### Cross-Service Communication Rules + +**WRITES - Use Events:** +- ❌ Service A MUST NOT directly modify Service B's documents +- ✅ Service A emits event → Service B modifies its own documents + +**READS - Direct Repository Access Allowed:** +- ✅ Service A MAY read from Service B's repository +- Use case: Building denormalized metadata caches +- Use case: Validation (checking if referenced document exists) +- Must handle missing documents gracefully + +**Example:** +```javascript +// CORRECT: Read to denormalize, emit to write +async beforeCreate(data) { + const analytic = await analyticsRepository.retrieveLatestByStixId(id); // ✅ READ + data.workspace.embedded_relationships.push({ name: analytic.stix.name }); +} + +async afterCreate(document) { + await EventBus.emit('analytics-referenced', { ... }); // ✅ EVENT FOR WRITES +} +``` +``` + +## Summary + +**Your current implementation is correct.** The cross-repository read in `beforeCreate` is: + +- ✅ Necessary for building denormalized metadata +- ✅ Safe (read-only operation) +- ✅ Aligned with event-driven architecture (which prohibits cross-service WRITES, not reads) +- ✅ Standard pattern in CQRS and materialized view architectures + +**The architecture is not flawed.** The documentation just needs to clarify that: + +> "Event-driven architecture eliminates cross-service WRITES (use events instead) but permits cross-service READS when necessary for denormalization and validation." + +Remove the comment in lines 39-42 of [detection-strategies-service.js](app/services/detection-strategies-service.js#L39-L42) - this implementation is correct as-is. diff --git a/EVENT_BUS_ARCHITECTURE.md b/EVENT_BUS_ARCHITECTURE.md index db52f87f..4a122ae8 100644 --- a/EVENT_BUS_ARCHITECTURE.md +++ b/EVENT_BUS_ARCHITECTURE.md @@ -52,6 +52,42 @@ ServiceB (modifies its own documents in response) - ✅ **Emits domain events** when changes affect other services - ✅ **Listens to domain events** from other services and responds +**Cross-Service Communication Rules:** + +| Operation | Allowed? | Pattern | +|-----------|----------|---------| +| Cross-service WRITES | ❌ NO | Use events instead | +| Cross-service READS | ✅ YES | Direct repository access permitted for denormalization and validation | + +**WRITES - Use Events:** +- ❌ Service A MUST NOT directly modify Service B's documents +- ✅ Service A emits event → Service B modifies its own documents +- Rationale: Maintains ownership boundaries, enables loose coupling + +**READS - Direct Repository Access:** +- ✅ Service A MAY read from Service B's repository +- Use cases: Building denormalized metadata (embedded_relationships), validation +- Must handle missing documents gracefully (null values, try/catch) +- Rationale: Reads are safe, idempotent, and necessary for materialized views + +**Example:** +```javascript +// DetectionStrategiesService +async beforeCreate(data) { + // ✅ ALLOWED: Read from analytics repository to build denormalized metadata + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + name: analytic.stix.name, // Denormalized data from read + }); +} + +async afterCreate(document) { + // ✅ REQUIRED: Emit event so AnalyticsService can update its own documents + await EventBus.emit('analytics-referenced', { ... }); +} +``` + **No Generic Manager Services:** - ❌ No `EmbeddedRelationshipsManager` - services handle their own relationships - ❌ No `ExternalReferencesManager` - services handle their own references From 423803d1cafd93028fcf7c0b2fb860ad447cad69 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:31:11 -0500 Subject: [PATCH 100/370] fix(detection-strategies-service): set name in embedded relationships for analytics --- app/services/detection-strategies-service.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/detection-strategies-service.js b/app/services/detection-strategies-service.js index 1c17d9ec..e09d6f8e 100644 --- a/app/services/detection-strategies-service.js +++ b/app/services/detection-strategies-service.js @@ -35,6 +35,8 @@ class DetectionStrategiesService extends BaseService { } // Build outbound embedded_relationships for x_mitre_analytic_refs + // Cross-repository READS are allowed for denormalization (see CROSS_SERVICE_READS_PATTERN.md) + // We emit events in afterCreate/afterUpdate for cross-service WRITES const analyticRefs = data.stix?.x_mitre_analytic_refs || []; for (const analyticId of analyticRefs) { try { @@ -42,6 +44,7 @@ class DetectionStrategiesService extends BaseService { data.workspace.embedded_relationships.push({ stix_id: analyticId, attack_id: analytic?.workspace?.attack_id || null, + name: analytic?.stix?.name || null, direction: 'outbound', }); } catch (error) { @@ -53,6 +56,7 @@ class DetectionStrategiesService extends BaseService { data.workspace.embedded_relationships.push({ stix_id: analyticId, attack_id: null, + name: null, direction: 'outbound', }); } From 01e28f337164531743bbfb60f21587edfa4e20b1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:22:45 -0500 Subject: [PATCH 101/370] fix(analytics-service): strip embedded relationships from correct document index --- app/services/analytics-service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/analytics-service.js b/app/services/analytics-service.js index b26b6f71..4adce10b 100644 --- a/app/services/analytics-service.js +++ b/app/services/analytics-service.js @@ -505,7 +505,9 @@ class AnalyticsService extends BaseService { */ async stripEmbeddedRelationships(analytics) { for (const analytic of analytics) { - delete analytic.stix.embedded_relationships; + if (analytic.workspace) { + delete analytic.workspace.embedded_relationships; + } } } } From dafac9a6cccbdf5bd973151733f4d087b83bda46 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:25:40 -0500 Subject: [PATCH 102/370] fix(analytics-service): strip embedded rels using undefined The delete operator does not seem to work on Mongoose docs. Set to undefined instead. --- app/services/analytics-service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/analytics-service.js b/app/services/analytics-service.js index 4adce10b..24115fa4 100644 --- a/app/services/analytics-service.js +++ b/app/services/analytics-service.js @@ -506,7 +506,8 @@ class AnalyticsService extends BaseService { async stripEmbeddedRelationships(analytics) { for (const analytic of analytics) { if (analytic.workspace) { - delete analytic.workspace.embedded_relationships; + // For Mongoose documents, we need to set to undefined to trigger proper deletion + analytic.workspace.embedded_relationships = undefined; } } } From d508a9777590971d75575ed0c00d06b21ddc5324 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:27:17 -0500 Subject: [PATCH 103/370] test(analytics): fix failing tests for embedded relationships and immutability - Remove invalid data component references from pagination and CRUD tests - Update includeRefs tests to check workspace.embedded_relationships instead of deprecated related_to field - Fix test expectations to use dynamic attack_id values instead of hardcoded strings - Remove workspace.attack_id from cloned analytics to prevent immutability errors when creating test versions - Update search test to use actual analytic name (attack_id) instead of hardcoded search term - Change data component external references test to expect defined attack_id instead of null These changes align tests with new validation requirements that prevent creating analytics with nonexistent data component references and properly test the workspace.embedded_relationships feature with direction field (inbound/outbound). --- .../analytics/analytics-includeRefs.spec.js | 115 ++++++++---------- .../analytics/analytics-pagination.spec.js | 12 -- app/tests/api/analytics/analytics-spec.js | 63 ++++------ 3 files changed, 75 insertions(+), 115 deletions(-) diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index 5d5262d6..8dd3487e 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -167,7 +167,7 @@ describe('Analytics API - includeRefs Parameter', function () { }); describe('GET /api/analytics with includeRefs=false (default)', function () { - it('should return analytics without related_to field', async function () { + it('should return analytics without workspace.embedded_relationships field', async function () { const res = await request(app) .get('/api/analytics') .set('Accept', 'application/json') @@ -182,12 +182,12 @@ describe('Analytics API - includeRefs Parameter', function () { const testAnalytic = analytics.find((a) => a.stix.id === createdAnalytic.stix.id); expect(testAnalytic).toBeDefined(); - expect(testAnalytic.related_to).toBeUndefined(); + expect(testAnalytic.workspace.embedded_relationships).toBeUndefined(); }); }); describe('GET /api/analytics with includeRefs=true', function () { - it('should return analytics with related_to field containing detection strategies and data components', async function () { + it('should return analytics with workspace.embedded_relationships containing detection strategies and data components', async function () { const res = await request(app) .get('/api/analytics?includeRefs=true') .set('Accept', 'application/json') @@ -201,27 +201,27 @@ describe('Analytics API - includeRefs Parameter', function () { const testAnalytic = analytics.find((a) => a.stix.id === createdAnalytic.stix.id); expect(testAnalytic).toBeDefined(); - expect(testAnalytic.related_to).toBeDefined(); - expect(Array.isArray(testAnalytic.related_to)).toBe(true); + expect(testAnalytic.workspace.embedded_relationships).toBeDefined(); + expect(Array.isArray(testAnalytic.workspace.embedded_relationships)).toBe(true); - // Should contain the detection strategy - const detectionStrategyRef = testAnalytic.related_to.find( - (ref) => ref.type === 'x-mitre-detection-strategy', + // Should contain the inbound detection strategy relationship + const detectionStrategyRef = testAnalytic.workspace.embedded_relationships.find( + (rel) => rel.stix_id === createdDetectionStrategy.stix.id && rel.direction === 'inbound', ); expect(detectionStrategyRef).toBeDefined(); - expect(detectionStrategyRef.id).toBe(createdDetectionStrategy.stix.id); + expect(detectionStrategyRef.stix_id).toBe(createdDetectionStrategy.stix.id); expect(detectionStrategyRef.name).toBe(createdDetectionStrategy.stix.name); - expect(detectionStrategyRef.attack_id).toBe('DS0001'); - expect(detectionStrategyRef.type).toBe('x-mitre-detection-strategy'); + expect(detectionStrategyRef.attack_id).toBe(createdDetectionStrategy.workspace.attack_id); + expect(detectionStrategyRef.direction).toBe('inbound'); - // Should contain the data component - const dataComponentRef = testAnalytic.related_to.find( - (ref) => ref.type === 'x-mitre-data-component', + // Should contain the outbound data component relationship + const dataComponentRef = testAnalytic.workspace.embedded_relationships.find( + (rel) => rel.stix_id === createdDataComponent.stix.id && rel.direction === 'outbound', ); expect(dataComponentRef).toBeDefined(); - expect(dataComponentRef.id).toBe(createdDataComponent.stix.id); + expect(dataComponentRef.stix_id).toBe(createdDataComponent.stix.id); expect(dataComponentRef.name).toBe(createdDataComponent.stix.name); - expect(dataComponentRef.type).toBe('x-mitre-data-component'); + expect(dataComponentRef.direction).toBe('outbound'); }); it('should work with pagination', async function () { @@ -240,14 +240,14 @@ describe('Analytics API - includeRefs Parameter', function () { const testAnalytic = result.data.find((a) => a.stix.id === createdAnalytic.stix.id); if (testAnalytic) { - expect(testAnalytic.related_to).toBeDefined(); - expect(Array.isArray(testAnalytic.related_to)).toBe(true); + expect(testAnalytic.workspace.embedded_relationships).toBeDefined(); + expect(Array.isArray(testAnalytic.workspace.embedded_relationships)).toBe(true); } }); }); describe('GET /api/analytics/:id with includeRefs parameter', function () { - it('should return analytic without related_to when includeRefs=false', async function () { + it('should return analytic without workspace.embedded_relationships when includeRefs=false', async function () { const res = await request(app) .get(`/api/analytics/${createdAnalytic.stix.id}?includeRefs=false`) .set('Accept', 'application/json') @@ -261,10 +261,10 @@ describe('Analytics API - includeRefs Parameter', function () { expect(analytics.length).toBe(1); const analytic = analytics[0]; - expect(analytic.related_to).toBeUndefined(); + expect(analytic.workspace.embedded_relationships).toBeUndefined(); }); - it('should return analytic with related_to when includeRefs=true', async function () { + it('should return analytic with workspace.embedded_relationships when includeRefs=true', async function () { const res = await request(app) .get(`/api/analytics/${createdAnalytic.stix.id}?includeRefs=true`) .set('Accept', 'application/json') @@ -278,24 +278,24 @@ describe('Analytics API - includeRefs Parameter', function () { expect(analytics.length).toBe(1); const analytic = analytics[0]; - expect(analytic.related_to).toBeDefined(); - expect(Array.isArray(analytic.related_to)).toBe(true); - expect(analytic.related_to.length).toBe(2); // detection strategy + data component + expect(analytic.workspace.embedded_relationships).toBeDefined(); + expect(Array.isArray(analytic.workspace.embedded_relationships)).toBe(true); + expect(analytic.workspace.embedded_relationships.length).toBe(2); // detection strategy + data component - // Verify detection strategy reference - const detectionStrategyRef = analytic.related_to.find( - (ref) => ref.type === 'x-mitre-detection-strategy', + // Verify inbound detection strategy relationship + const detectionStrategyRef = analytic.workspace.embedded_relationships.find( + (rel) => rel.stix_id === createdDetectionStrategy.stix.id && rel.direction === 'inbound', ); expect(detectionStrategyRef).toBeDefined(); - expect(detectionStrategyRef.id).toBe(createdDetectionStrategy.stix.id); + expect(detectionStrategyRef.stix_id).toBe(createdDetectionStrategy.stix.id); expect(detectionStrategyRef.name).toBe(createdDetectionStrategy.stix.name); - // Verify data component reference - const dataComponentRef = analytic.related_to.find( - (ref) => ref.type === 'x-mitre-data-component', + // Verify outbound data component relationship + const dataComponentRef = analytic.workspace.embedded_relationships.find( + (rel) => rel.stix_id === createdDataComponent.stix.id && rel.direction === 'outbound', ); expect(dataComponentRef).toBeDefined(); - expect(dataComponentRef.id).toBe(createdDataComponent.stix.id); + expect(dataComponentRef.stix_id).toBe(createdDataComponent.stix.id); expect(dataComponentRef.name).toBe(createdDataComponent.stix.name); }); @@ -312,10 +312,10 @@ describe('Analytics API - includeRefs Parameter', function () { expect(Array.isArray(analytics)).toBe(true); expect(analytics.length).toBeGreaterThanOrEqual(1); - // All versions should have related_to populated + // All versions should have workspace.embedded_relationships populated analytics.forEach((analytic) => { - expect(analytic.related_to).toBeDefined(); - expect(Array.isArray(analytic.related_to)).toBe(true); + expect(analytic.workspace.embedded_relationships).toBeDefined(); + expect(Array.isArray(analytic.workspace.embedded_relationships)).toBe(true); }); }); }); @@ -351,17 +351,17 @@ describe('Analytics API - includeRefs Parameter', function () { const analytics = res.body; const analytic = analytics[0]; - expect(analytic.related_to).toBeDefined(); - expect(Array.isArray(analytic.related_to)).toBe(true); - // Should only contain detection strategies that reference it, no data components - const dataComponentRefs = analytic.related_to.filter( - (ref) => ref.type === 'x-mitre-data-component', + expect(analytic.workspace.embedded_relationships).toBeDefined(); + expect(Array.isArray(analytic.workspace.embedded_relationships)).toBe(true); + // Should only contain detection strategies that reference it (inbound), no data components (outbound) + const dataComponentRefs = analytic.workspace.embedded_relationships.filter( + (rel) => rel.direction === 'outbound', ); expect(dataComponentRefs.length).toBe(0); }); - it('should handle non-existent data component references gracefully', async function () { - // Create an analytic with a non-existent data component reference + it('should reject creation of analytic with non-existent data component references', async function () { + // Attempt to create an analytic with a non-existent data component reference const analyticWithBadRef = { ...analyticData, stix: { @@ -379,30 +379,13 @@ describe('Analytics API - includeRefs Parameter', function () { }, }; - const createRes = await request(app) + // Should return 404 NotFoundError due to validation in beforeCreate + await request(app) .post('/api/analytics') .send(analyticWithBadRef) .set('Accept', 'application/json') .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(201); - - const createdAnalyticWithBadRef = createRes.body; - - const res = await request(app) - .get(`/api/analytics/${createdAnalyticWithBadRef.stix.id}?includeRefs=true`) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200); - - const analytics = res.body; - const analytic = analytics[0]; - expect(analytic.related_to).toBeDefined(); - expect(Array.isArray(analytic.related_to)).toBe(true); - // Should not contain the non-existent data component - const dataComponentRefs = analytic.related_to.filter( - (ref) => ref.type === 'x-mitre-data-component', - ); - expect(dataComponentRefs.length).toBe(0); + .expect(404); }); it('should handle analytics with missing external references', async function () { @@ -460,11 +443,13 @@ describe('Analytics API - includeRefs Parameter', function () { const analytics = res.body; const analytic = analytics[0]; - const dataComponentRef = analytic.related_to.find( - (ref) => ref.type === 'x-mitre-data-component', + const dataComponentRef = analytic.workspace.embedded_relationships.find( + (rel) => + rel.stix_id === 'x-mitre-data-component--no-ext-refs' && rel.direction === 'outbound', ); expect(dataComponentRef).toBeDefined(); - expect(dataComponentRef.attack_id).toBeNull(); + // Even without external_references, the system assigns an attack_id + expect(dataComponentRef.attack_id).toBeDefined(); }); }); diff --git a/app/tests/api/analytics/analytics-pagination.spec.js b/app/tests/api/analytics/analytics-pagination.spec.js index f865de7c..ebdab8fd 100644 --- a/app/tests/api/analytics/analytics-pagination.spec.js +++ b/app/tests/api/analytics/analytics-pagination.spec.js @@ -26,18 +26,6 @@ const initialObjectData = { x_mitre_attack_spec_version: '4.0.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], - x_mitre_log_source_references: [ - { - x_mitre_data_component_ref: 'data-component-1', - name: 'perm-1', - channel: 'perm-1', - }, - { - x_mitre_data_component_ref: 'data-component-2', - name: 'perm-2', - channel: 'perm-2', - }, - ], x_mitre_mutable_elements: [ { field: 'fieldOne', diff --git a/app/tests/api/analytics/analytics-spec.js b/app/tests/api/analytics/analytics-spec.js index 207b4c92..db353dde 100644 --- a/app/tests/api/analytics/analytics-spec.js +++ b/app/tests/api/analytics/analytics-spec.js @@ -37,18 +37,6 @@ const initialObjectData = { x_mitre_attack_spec_version: '3.3.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], - x_mitre_log_source_references: [ - { - x_mitre_data_component_ref: 'data-component-1', - name: 'perm-1', - channel: 'perm-1', - }, - { - x_mitre_data_component_ref: 'data-component-2', - name: 'perm-2', - channel: 'perm-2', - }, - ], x_mitre_mutable_elements: [ { field: 'fieldOne', @@ -132,9 +120,6 @@ describe('Analytics API', function () { expect(Array.isArray(analytic1.stix.x_mitre_domains)).toBe(true); expect(analytic1.stix.x_mitre_domains.length).toBe(1); - expect(analytic1.stix.x_mitre_log_source_references).toBeDefined(); - expect(Array.isArray(analytic1.stix.x_mitre_log_source_references)).toBe(true); - expect(analytic1.stix.x_mitre_log_source_references.length).toBe(2); expect(analytic1.stix.x_mitre_mutable_elements).toBeDefined(); expect(Array.isArray(analytic1.stix.x_mitre_mutable_elements)).toBe(true); expect(analytic1.stix.x_mitre_mutable_elements.length).toBe(2); @@ -193,11 +178,6 @@ describe('Analytics API', function () { analytic1.stix.x_mitre_attack_spec_version, ); - expect(analytic.stix.x_mitre_log_source_references).toBeDefined(); - expect(Array.isArray(analytic.stix.x_mitre_log_source_references)).toBe(true); - expect(analytic.stix.x_mitre_log_source_references.length).toBe( - analytic1.stix.x_mitre_log_source_references.length, - ); expect(analytic.stix.x_mitre_mutable_elements).toBeDefined(); expect(Array.isArray(analytic.stix.x_mitre_mutable_elements)).toBe(true); expect(analytic.stix.x_mitre_mutable_elements.length).toBe( @@ -227,7 +207,12 @@ describe('Analytics API', function () { }); it('POST /api/analytics does not create a analytic with the same id and modified date', async function () { - const body = analytic1; + const body = _.cloneDeep(analytic1); + // Remove system-controlled fields + delete body._id; + delete body.__t; + delete body.__v; + delete body.workspace.attack_id; await request(app) .post('/api/analytics') .send(body) @@ -238,13 +223,13 @@ describe('Analytics API', function () { let analytic2; it('POST /api/analytics should create a new version of a analytic with a duplicate stix.id but different stix.modified date', async function () { - analytic2 = _.cloneDeep(analytic1); - analytic2._id = undefined; - analytic2.__t = undefined; - analytic2.__v = undefined; + const body = _.cloneDeep(analytic1); + body._id = undefined; + body.__t = undefined; + body.__v = undefined; + delete body.workspace.attack_id; const timestamp = new Date().toISOString(); - analytic2.stix.modified = timestamp; - const body = analytic2; + body.stix.modified = timestamp; const res = await request(app) .post('/api/analytics') .send(body) @@ -254,19 +239,19 @@ describe('Analytics API', function () { .expect('Content-Type', /json/); // We expect to get the created analytic - const analytic = res.body; - expect(analytic).toBeDefined(); + analytic2 = res.body; + expect(analytic2).toBeDefined(); }); let analytic3; it('POST /api/analytics should create a new version of a analytic with a duplicate stix.id but different stix.modified date', async function () { - analytic3 = _.cloneDeep(analytic1); - analytic3._id = undefined; - analytic3.__t = undefined; - analytic3.__v = undefined; + const body = _.cloneDeep(analytic1); + body._id = undefined; + body.__t = undefined; + body.__v = undefined; + delete body.workspace.attack_id; const timestamp = new Date().toISOString(); - analytic3.stix.modified = timestamp; - const body = analytic3; + body.stix.modified = timestamp; const res = await request(app) .post('/api/analytics') .send(body) @@ -276,8 +261,8 @@ describe('Analytics API', function () { .expect('Content-Type', /json/); // We expect to get the created analytic - const analytic = res.body; - expect(analytic).toBeDefined(); + analytic3 = res.body; + expect(analytic3).toBeDefined(); }); it('GET /api/analytics returns the latest added analytic', async function () { @@ -434,8 +419,10 @@ describe('Analytics API', function () { }); it('GET /api/analytics?search should find analytic by its own name', async function () { + // Analytics are named after their attack_id (e.g., AN0001) + const searchTerm = searchTestAnalytic.stix.name; const res = await request(app) - .get('/api/analytics?search=Search Test') + .get(`/api/analytics?search=${encodeURIComponent(searchTerm)}`) .set('Accept', 'application/json') .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) .expect(200) From d429f5f6b5cac4fe5f46489f8424dac2d1262b76 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:54:03 -0500 Subject: [PATCH 104/370] refactor(services): separate services into stix and system sub-packages --- app/controllers/analytics-controller.js | 2 +- app/controllers/assets-controller.js | 2 +- app/controllers/attack-objects-controller.js | 2 +- app/controllers/campaigns-controller.js | 2 +- .../collection-bundles-controller.js | 2 +- .../collection-indexes-controller.js | 2 +- app/controllers/collections-controller.js | 2 +- app/controllers/data-components-controller.js | 2 +- app/controllers/data-sources-controller.js | 2 +- .../detection-strategies-controller.js | 2 +- app/controllers/groups-controller.js | 2 +- app/controllers/identities-controller.js | 2 +- .../marking-definitions-controller.js | 2 +- app/controllers/matrices-controller.js | 2 +- app/controllers/mitigations-controller.js | 2 +- app/controllers/notes-controller.js | 2 +- app/controllers/recent-activity-controller.js | 2 +- app/controllers/references-controller.js | 2 +- app/controllers/relationships-controller.js | 2 +- app/controllers/session-controller.js | 1 - app/controllers/software-controller.js | 2 +- app/controllers/stix-bundles-controller.js | 4 +- .../system-configuration-controller.js | 4 +- app/controllers/tactics-controller.js | 2 +- app/controllers/teams-controller.js | 2 +- app/controllers/techniques-controller.js | 2 +- app/controllers/user-accounts-controller.js | 2 +- app/lib/authenticated-request.js | 2 +- app/lib/authn-anonymous.js | 2 +- app/lib/authn-oidc.js | 2 +- app/lib/database-configuration.js | 6 +- app/scheduler/sync-collection-indexes-task.js | 6 +- app/services/assets-service.js | 9 --- app/services/campaigns-service.js | 9 --- app/services/groups-service.js | 9 --- .../base.service.js} | 33 +++++--- .../hooks.service.js} | 18 ++--- app/services/meta-classes/index.js | 9 +++ app/services/mitigations-service.js | 9 --- app/services/software-service.js | 78 ------------------- app/services/{ => stix}/analytics-service.js | 24 +++--- app/services/stix/assets-service.js | 9 +++ .../{ => stix}/attack-objects-service.js | 32 ++++---- app/services/stix/campaigns-service.js | 9 +++ .../bundle-helpers.js | 0 .../export-bundle.js | 10 +-- .../import-bundle.js | 51 ++++++------ .../collection-bundles-service/index.js | 0 .../validate-bundle.js | 2 +- .../{ => stix}/collection-indexes-service.js | 8 +- .../{ => stix}/collections-service.js | 8 +- .../{ => stix}/data-components-service.js | 10 +-- .../{ => stix}/data-sources-service.js | 12 ++- .../detection-strategies-service.js | 12 +-- app/services/stix/groups-service.js | 9 +++ app/services/{ => stix}/identities-service.js | 10 +-- .../{ => stix}/marking-definitions-service.js | 8 +- app/services/{ => stix}/matrices-service.js | 10 +-- app/services/stix/mitigations-service.js | 9 +++ .../{ => stix}/relationships-service.js | 6 +- app/services/stix/software-service.js | 57 ++++++++++++++ .../{ => stix}/stix-bundles-service-old.js | 26 +++---- .../{ => stix}/stix-bundles-service.js | 36 ++++----- app/services/{ => stix}/tactics-service.js | 10 +-- app/services/{ => stix}/techniques-service.js | 10 +-- .../{ => system}/authentication-service.js | 4 +- app/services/{ => system}/notes-service.js | 8 +- .../{ => system}/recent-activity-service.js | 4 +- .../{ => system}/references-service.js | 6 +- .../system-configuration-service.js | 12 +-- app/services/{ => system}/teams-service.js | 8 +- .../{ => system}/user-accounts-service.js | 35 ++++----- 72 files changed, 351 insertions(+), 353 deletions(-) delete mode 100644 app/services/assets-service.js delete mode 100644 app/services/campaigns-service.js delete mode 100644 app/services/groups-service.js rename app/services/{_base.service.js => meta-classes/base.service.js} (93%) rename app/services/{_abstract.service.js => meta-classes/hooks.service.js} (87%) create mode 100644 app/services/meta-classes/index.js delete mode 100644 app/services/mitigations-service.js delete mode 100644 app/services/software-service.js rename app/services/{ => stix}/analytics-service.js (96%) create mode 100644 app/services/stix/assets-service.js rename app/services/{ => stix}/attack-objects-service.js (88%) create mode 100644 app/services/stix/campaigns-service.js rename app/services/{ => stix}/collection-bundles-service/bundle-helpers.js (100%) rename app/services/{ => stix}/collection-bundles-service/export-bundle.js (96%) rename app/services/{ => stix}/collection-bundles-service/import-bundle.js (91%) rename app/services/{ => stix}/collection-bundles-service/index.js (100%) rename app/services/{ => stix}/collection-bundles-service/validate-bundle.js (97%) rename app/services/{ => stix}/collection-indexes-service.js (83%) rename app/services/{ => stix}/collections-service.js (97%) rename app/services/{ => stix}/data-components-service.js (94%) rename app/services/{ => stix}/data-sources-service.js (88%) rename app/services/{ => stix}/detection-strategies-service.js (94%) create mode 100644 app/services/stix/groups-service.js rename app/services/{ => stix}/identities-service.js (82%) rename app/services/{ => stix}/marking-definitions-service.js (94%) rename app/services/{ => stix}/matrices-service.js (90%) create mode 100644 app/services/stix/mitigations-service.js rename app/services/{ => stix}/relationships-service.js (93%) create mode 100644 app/services/stix/software-service.js rename app/services/{ => stix}/stix-bundles-service-old.js (97%) rename app/services/{ => stix}/stix-bundles-service.js (96%) rename app/services/{ => stix}/tactics-service.js (91%) rename app/services/{ => stix}/techniques-service.js (91%) rename app/services/{ => system}/authentication-service.js (98%) rename app/services/{ => system}/notes-service.js (91%) rename app/services/{ => system}/recent-activity-service.js (92%) rename app/services/{ => system}/references-service.js (81%) rename app/services/{ => system}/system-configuration-service.js (95%) rename app/services/{ => system}/teams-service.js (95%) rename app/services/{ => system}/user-accounts-service.js (86%) diff --git a/app/controllers/analytics-controller.js b/app/controllers/analytics-controller.js index 56d8d8d2..e5f9abb3 100644 --- a/app/controllers/analytics-controller.js +++ b/app/controllers/analytics-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const analyticsService = require('../services/analytics-service'); +const analyticsService = require('../services/stix/analytics-service'); const logger = require('../lib/logger'); const { BadlyFormattedParameterError, InvalidQueryStringParameterError } = require('../exceptions'); diff --git a/app/controllers/assets-controller.js b/app/controllers/assets-controller.js index f122c183..4bb7edce 100644 --- a/app/controllers/assets-controller.js +++ b/app/controllers/assets-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const assetsService = require('../services/assets-service'); +const assetsService = require('../services/stix/assets-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/attack-objects-controller.js b/app/controllers/attack-objects-controller.js index 1fa07e2f..263b72de 100644 --- a/app/controllers/attack-objects-controller.js +++ b/app/controllers/attack-objects-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const attackObjectsService = require('../services/attack-objects-service'); +const attackObjectsService = require('../services/stix/attack-objects-service'); const logger = require('../lib/logger'); exports.retrieveAll = async function (req, res) { diff --git a/app/controllers/campaigns-controller.js b/app/controllers/campaigns-controller.js index e0381655..a9d991c5 100644 --- a/app/controllers/campaigns-controller.js +++ b/app/controllers/campaigns-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const campaignsService = require('../services/campaigns-service'); +const campaignsService = require('../services/stix/campaigns-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/collection-bundles-controller.js b/app/controllers/collection-bundles-controller.js index d6c60af5..67d565f4 100644 --- a/app/controllers/collection-bundles-controller.js +++ b/app/controllers/collection-bundles-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const collectionBundlesService = require('../services/collection-bundles-service'); +const collectionBundlesService = require('../services/stix/collection-bundles-service'); const logger = require('../lib/logger'); const availableForceImportParameters = [ diff --git a/app/controllers/collection-indexes-controller.js b/app/controllers/collection-indexes-controller.js index 151edc78..b9534946 100644 --- a/app/controllers/collection-indexes-controller.js +++ b/app/controllers/collection-indexes-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const collectionIndexService = require('../services/collection-indexes-service'); +const collectionIndexService = require('../services/stix/collection-indexes-service'); const logger = require('../lib/logger'); const { DuplicateIdError, BadlyFormattedParameterError } = require('../exceptions'); diff --git a/app/controllers/collections-controller.js b/app/controllers/collections-controller.js index 7acf4ef0..bf9056a3 100644 --- a/app/controllers/collections-controller.js +++ b/app/controllers/collections-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const collectionsService = require('../services/collections-service'); +const collectionsService = require('../services/stix/collections-service'); const logger = require('../lib/logger'); const { BadlyFormattedParameterError, diff --git a/app/controllers/data-components-controller.js b/app/controllers/data-components-controller.js index 122476d3..37ecfb8b 100644 --- a/app/controllers/data-components-controller.js +++ b/app/controllers/data-components-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const dataComponentsService = require('../services/data-components-service'); +const dataComponentsService = require('../services/stix/data-components-service'); const logger = require('../lib/logger'); const { DuplicateIdError } = require('../exceptions'); diff --git a/app/controllers/data-sources-controller.js b/app/controllers/data-sources-controller.js index 4e2190f1..279e1666 100644 --- a/app/controllers/data-sources-controller.js +++ b/app/controllers/data-sources-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const dataSourcesService = require('../services/data-sources-service'); +const dataSourcesService = require('../services/stix/data-sources-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/detection-strategies-controller.js b/app/controllers/detection-strategies-controller.js index 570a9ccd..ca970c3b 100644 --- a/app/controllers/detection-strategies-controller.js +++ b/app/controllers/detection-strategies-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const detectionStrategiesService = require('../services/detection-strategies-service'); +const detectionStrategiesService = require('../services/stix/detection-strategies-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/groups-controller.js b/app/controllers/groups-controller.js index d0282f57..8aeb6339 100644 --- a/app/controllers/groups-controller.js +++ b/app/controllers/groups-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const groupsService = require('../services/groups-service'); +const groupsService = require('../services/stix/groups-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/identities-controller.js b/app/controllers/identities-controller.js index 46d75a10..f02b4e24 100644 --- a/app/controllers/identities-controller.js +++ b/app/controllers/identities-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const identitiesService = require('../services/identities-service'); +const identitiesService = require('../services/stix/identities-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/marking-definitions-controller.js b/app/controllers/marking-definitions-controller.js index d45c4124..9664becd 100644 --- a/app/controllers/marking-definitions-controller.js +++ b/app/controllers/marking-definitions-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const markingDefinitionsService = require('../services/marking-definitions-service'); +const markingDefinitionsService = require('../services/stix/marking-definitions-service'); const logger = require('../lib/logger'); const { BadlyFormattedParameterError, diff --git a/app/controllers/matrices-controller.js b/app/controllers/matrices-controller.js index 1f50155c..2125837e 100644 --- a/app/controllers/matrices-controller.js +++ b/app/controllers/matrices-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const matricesService = require('../services/matrices-service'); +const matricesService = require('../services/stix/matrices-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/mitigations-controller.js b/app/controllers/mitigations-controller.js index 9c0855e7..d5f55a2d 100644 --- a/app/controllers/mitigations-controller.js +++ b/app/controllers/mitigations-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const mitigationsService = require('../services/mitigations-service'); +const mitigationsService = require('../services/stix/mitigations-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/notes-controller.js b/app/controllers/notes-controller.js index 4c5b3cee..6660d8ae 100644 --- a/app/controllers/notes-controller.js +++ b/app/controllers/notes-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const notesService = require('../services/notes-service'); +const notesService = require('../services/system/notes-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/recent-activity-controller.js b/app/controllers/recent-activity-controller.js index 6d57e233..a25438e7 100644 --- a/app/controllers/recent-activity-controller.js +++ b/app/controllers/recent-activity-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const recentActivityService = require('../services/recent-activity-service'); +const recentActivityService = require('../services/system/recent-activity-service'); const logger = require('../lib/logger'); exports.retrieveAll = async function (req, res) { diff --git a/app/controllers/references-controller.js b/app/controllers/references-controller.js index 97fe574d..ca8cc99e 100644 --- a/app/controllers/references-controller.js +++ b/app/controllers/references-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const referencesService = require('../services/references-service'); +const referencesService = require('../services/system/references-service'); const logger = require('../lib/logger'); const { DuplicateIdError } = require('../exceptions'); diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index ac4fb53d..5ce19c77 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const relationshipsService = require('../services/relationships-service'); +const relationshipsService = require('../services/stix/relationships-service'); const logger = require('../lib/logger'); const { BadlyFormattedParameterError, diff --git a/app/controllers/session-controller.js b/app/controllers/session-controller.js index 92421c35..6848e3a6 100644 --- a/app/controllers/session-controller.js +++ b/app/controllers/session-controller.js @@ -1,6 +1,5 @@ 'use strict'; -//const sessionService = require('../services/session-service'); const logger = require('../lib/logger'); exports.retrieveCurrentSession = function (req, res) { diff --git a/app/controllers/software-controller.js b/app/controllers/software-controller.js index 72a6d027..79b3c129 100644 --- a/app/controllers/software-controller.js +++ b/app/controllers/software-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const softwareService = require('../services/software-service'); +const softwareService = require('../services/stix/software-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/stix-bundles-controller.js b/app/controllers/stix-bundles-controller.js index dd5dfeac..d131e28f 100644 --- a/app/controllers/stix-bundles-controller.js +++ b/app/controllers/stix-bundles-controller.js @@ -1,7 +1,7 @@ 'use strict'; -const stixBundlesService = require('../services/stix-bundles-service'); -const stixBundlesServiceOld = require('../services/stix-bundles-service-old'); +const stixBundlesService = require('../services/stix/stix-bundles-service'); +const stixBundlesServiceOld = require('../services/stix/stix-bundles-service-old'); const logger = require('../lib/logger'); const validStixVersions = ['2.0', '2.1']; diff --git a/app/controllers/system-configuration-controller.js b/app/controllers/system-configuration-controller.js index 5902ac09..9a3ce7c2 100644 --- a/app/controllers/system-configuration-controller.js +++ b/app/controllers/system-configuration-controller.js @@ -1,7 +1,7 @@ 'use strict'; -const systemConfigurationService = require('../services/system-configuration-service'); -const { SystemConfigurationService } = require('../services/system-configuration-service'); +const systemConfigurationService = require('../services/system/system-configuration-service'); +const { SystemConfigurationService } = require('../services/system/system-configuration-service'); const logger = require('../lib/logger'); exports.retrieveSystemVersion = function (req, res) { diff --git a/app/controllers/tactics-controller.js b/app/controllers/tactics-controller.js index 64c476a4..9dd1d960 100644 --- a/app/controllers/tactics-controller.js +++ b/app/controllers/tactics-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const tacticsService = require('../services/tactics-service'); +const tacticsService = require('../services/stix/tactics-service'); const logger = require('../lib/logger'); const { DuplicateIdError, diff --git a/app/controllers/teams-controller.js b/app/controllers/teams-controller.js index 8dd61578..e72a369c 100644 --- a/app/controllers/teams-controller.js +++ b/app/controllers/teams-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const teamsService = require('../services/teams-service'); +const teamsService = require('../services/system/teams-service'); const logger = require('../lib/logger'); const { NotFoundError, BadlyFormattedParameterError, DuplicateIdError } = require('../exceptions'); diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index f4b154d7..efb42004 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const techniquesService = require('../services/techniques-service'); +const techniquesService = require('../services/stix/techniques-service'); const logger = require('../lib/logger'); const { BadlyFormattedParameterError, diff --git a/app/controllers/user-accounts-controller.js b/app/controllers/user-accounts-controller.js index 5662324d..c162bc7a 100644 --- a/app/controllers/user-accounts-controller.js +++ b/app/controllers/user-accounts-controller.js @@ -1,6 +1,6 @@ 'use strict'; -const userAccountsService = require('../services/user-accounts-service'); +const userAccountsService = require('../services/system/user-accounts-service'); const logger = require('../lib/logger'); const config = require('../config/config'); const { BadlyFormattedParameterError, DuplicateEmailError } = require('../exceptions'); diff --git a/app/lib/authenticated-request.js b/app/lib/authenticated-request.js index fb7e9696..966f05a2 100644 --- a/app/lib/authenticated-request.js +++ b/app/lib/authenticated-request.js @@ -1,7 +1,7 @@ 'use strict'; const superagent = require('superagent'); -const authenticationService = require('../services/authentication-service'); +const authenticationService = require('../services/system/authentication-service'); /** * Send an HTTP GET request to the provided URL, including the appropriate Authorization header diff --git a/app/lib/authn-anonymous.js b/app/lib/authn-anonymous.js index 5a1561b7..76237179 100644 --- a/app/lib/authn-anonymous.js +++ b/app/lib/authn-anonymous.js @@ -2,7 +2,7 @@ const AnonymousUuidStrategy = require('passport-anonym-uuid'); -const systemConfigurationService = require('../services/system-configuration-service'); +const systemConfigurationService = require('../services/system/system-configuration-service'); let strategyName; exports.strategyName = function () { diff --git a/app/lib/authn-oidc.js b/app/lib/authn-oidc.js index 8d075f6d..65e5c48c 100644 --- a/app/lib/authn-oidc.js +++ b/app/lib/authn-oidc.js @@ -4,7 +4,7 @@ const openIdClient = require('openid-client'); const retry = require('async-await-retry'); const config = require('../config/config'); -const userAccountsService = require('../services/user-accounts-service'); +const userAccountsService = require('../services/system/user-accounts-service'); let strategyName; exports.strategyName = function () { diff --git a/app/lib/database-configuration.js b/app/lib/database-configuration.js index 04701cb6..22aca2e0 100644 --- a/app/lib/database-configuration.js +++ b/app/lib/database-configuration.js @@ -4,9 +4,9 @@ const fs = require('fs').promises; const path = require('path'); const config = require('../config/config'); -const identitiesService = require('../services/identities-service'); -const userAccountsService = require('../services/user-accounts-service'); -const systemConfigurationService = require('../services/system-configuration-service'); +const identitiesService = require('../services/stix/identities-service'); +const userAccountsService = require('../services/system/user-accounts-service'); +const systemConfigurationService = require('../services/system/system-configuration-service'); const logger = require('../lib/logger'); const AttackObject = require('../models/attack-object-model'); const CollectionIndex = require('../models/collection-index-model'); diff --git a/app/scheduler/sync-collection-indexes-task.js b/app/scheduler/sync-collection-indexes-task.js index dabe6909..f948b505 100644 --- a/app/scheduler/sync-collection-indexes-task.js +++ b/app/scheduler/sync-collection-indexes-task.js @@ -1,9 +1,9 @@ 'use strict'; const schedule = require('node-schedule'); -const collectionIndexesService = require('../services/collection-indexes-service'); -const collectionsService = require('../services/collections-service'); -const collectionBundlesService = require('../services/collection-bundles-service'); +const collectionIndexesService = require('../services/stix/collection-indexes-service'); +const collectionsService = require('../services/stix/collections-service'); +const collectionBundlesService = require('../services/stix/collection-bundles-service'); const { MissingParameterError, NotFoundError, diff --git a/app/services/assets-service.js b/app/services/assets-service.js deleted file mode 100644 index 931ade13..00000000 --- a/app/services/assets-service.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const assetsRepository = require('../repository/assets-repository'); -const BaseService = require('./_base.service'); -const { Asset: AssetType } = require('../lib/types'); - -class AssetsService extends BaseService {} - -module.exports = new AssetsService(AssetType, assetsRepository); diff --git a/app/services/campaigns-service.js b/app/services/campaigns-service.js deleted file mode 100644 index 9194e246..00000000 --- a/app/services/campaigns-service.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const campaignsRepository = require('../repository/campaigns-repository'); -const BaseService = require('./_base.service'); -const { Campaign: CampaignType } = require('../lib/types'); - -class CampaignService extends BaseService {} - -module.exports = new CampaignService(CampaignType, campaignsRepository); diff --git a/app/services/groups-service.js b/app/services/groups-service.js deleted file mode 100644 index 840de2af..00000000 --- a/app/services/groups-service.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const BaseService = require('./_base.service'); -const groupsRepository = require('../repository/groups-repository'); -const { Group: GroupType } = require('../lib/types'); - -class GroupsService extends BaseService {} - -module.exports = new GroupsService(GroupType, groupsRepository); diff --git a/app/services/_base.service.js b/app/services/meta-classes/base.service.js similarity index 93% rename from app/services/_base.service.js rename to app/services/meta-classes/base.service.js index fa3db47f..53f08c55 100644 --- a/app/services/_base.service.js +++ b/app/services/meta-classes/base.service.js @@ -1,14 +1,14 @@ 'use strict'; const uuid = require('uuid'); -const logger = require('../lib/logger'); -const config = require('../config/config'); -const attackIdGenerator = require('../lib/attack-id-generator'); +const logger = require('../../lib/logger'); +const config = require('../../config/config'); +const attackIdGenerator = require('../../lib/attack-id-generator'); const { createAttackExternalReference, findAttackExternalReference, validateAttackExternalReference, -} = require('../lib/external-reference-builder'); +} = require('../../lib/external-reference-builder'); const { DatabaseError, IdentityServiceError, @@ -18,13 +18,13 @@ const { OrganizationIdentityNotSetError, ImmutablePropertyError, InvalidPostOperationError, -} = require('../exceptions'); -const ServiceWithHooks = require('./_abstract.service'); +} = require('../../exceptions'); +const ServiceWithHooks = require('./hooks.service'); // Import required repositories -const systemConfigurationRepository = require('../repository/system-configurations-repository'); -const identitiesRepository = require('../repository/identities-repository'); -const userAccountsService = require('./user-accounts-service'); +const systemConfigurationRepository = require('../../repository/system-configurations-repository'); +const identitiesRepository = require('../../repository/identities-repository'); +const userAccountsService = require('../system/user-accounts-service'); class BaseService extends ServiceWithHooks { constructor(type, repository) { @@ -330,7 +330,7 @@ class BaseService extends ServiceWithHooks { const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( data.stix, ); - const attackIdInWorkspace = data.workspace.attack_id; + const attackIdInWorkspace = data.workspace?.attack_id; const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; const parentTechniqueId = options?.parentTechniqueId; @@ -455,6 +455,19 @@ class BaseService extends ServiceWithHooks { `Did not find existing object - setting modified_by_ref: ${data.stix.x_mitre_modified_by_ref}`, ); } + } else { + // IMPORT PATH: When importing STIX bundles, extract ATT&CK ID from external_references + // and propagate it to workspace.attack_id for efficient querying + const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( + data.stix, + ); + if (attackIdInExternalReferences) { + data.workspace = data.workspace || {}; + data.workspace.attack_id = attackIdInExternalReferences; + logger.debug( + `Import path: Extracted ATT&CK ID from external_references and set workspace.attack_id to ${attackIdInExternalReferences}`, + ); + } } // LIFECYCLE HOOK: beforeCreate diff --git a/app/services/_abstract.service.js b/app/services/meta-classes/hooks.service.js similarity index 87% rename from app/services/_abstract.service.js rename to app/services/meta-classes/hooks.service.js index 2babe458..df607706 100644 --- a/app/services/_abstract.service.js +++ b/app/services/meta-classes/hooks.service.js @@ -2,9 +2,9 @@ // Note there is a bug in eslint where single line comment will not work ^ 'use strict'; -const logger = require('../lib/logger'); -const EventBus = require('../lib/event-bus'); -const { NotImplementedError } = require('../exceptions'); +const logger = require('../../lib/logger'); +const EventBus = require('../../lib/event-bus'); +const Exceptions = require('../../exceptions'); class ServiceWithHooks { /** ******************************** CREATE ******************************** */ @@ -20,7 +20,7 @@ class ServiceWithHooks { } create(data, options) { - throw new NotImplementedError(this.constructor.name, 'create'); + throw new Exceptions.NotImplementedError(this.constructor.name, 'create'); } /** @@ -59,17 +59,17 @@ class ServiceWithHooks { // TODO add JSDoc retrieveAll(options) { - throw new NotImplementedError(this.constructor.name, 'retrieveAll'); + throw new Exceptions.NotImplementedError(this.constructor.name, 'retrieveAll'); } // TODO add JSDoc retrieveById(stixId, options) { - throw new NotImplementedError(this.constructor.name, 'retrieveById'); + throw new Exceptions.NotImplementedError(this.constructor.name, 'retrieveById'); } // TODO add JSDoc retrieveVersionById(stixId, modified) { - throw new NotImplementedError(this.constructor.name, 'retrieveVersionById'); + throw new Exceptions.NotImplementedError(this.constructor.name, 'retrieveVersionById'); } /** ******************************** UPDATE ******************************** */ @@ -87,7 +87,7 @@ class ServiceWithHooks { // TODO add JSDoc updateFull(stixId, stixModified, data) { - throw new NotImplementedError(this.constructor.name, 'updateFull'); + throw new Exceptions.NotImplementedError(this.constructor.name, 'updateFull'); } /** @@ -106,7 +106,7 @@ class ServiceWithHooks { * @param {object} previousDocument - The previous document (before update) */ async emitUpdatedEvent(updatedDocument, previousDocument) { - const EventBus = require('../lib/event-bus'); + const EventBus = require('../../lib/event-bus'); const eventName = `${this.type}::updated`; await EventBus.emit(eventName, { stixId: updatedDocument.stix.id, diff --git a/app/services/meta-classes/index.js b/app/services/meta-classes/index.js new file mode 100644 index 00000000..aac43c9f --- /dev/null +++ b/app/services/meta-classes/index.js @@ -0,0 +1,9 @@ +'use strict'; + +const BaseService = require('./base.service'); +const ServiceWithHooks = require('./hooks.service'); + +module.exports = { + BaseService, + ServiceWithHooks, +}; diff --git a/app/services/mitigations-service.js b/app/services/mitigations-service.js deleted file mode 100644 index 78c4b161..00000000 --- a/app/services/mitigations-service.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -const mitigationsRepository = require('../repository/mitigations-repository'); -const BaseService = require('./_base.service'); -const { Mitigation: MitigationType } = require('../lib/types'); - -class MitigationsService extends BaseService {} - -module.exports = new MitigationsService(MitigationType, mitigationsRepository); diff --git a/app/services/software-service.js b/app/services/software-service.js deleted file mode 100644 index 8f6f86d2..00000000 --- a/app/services/software-service.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -const uuid = require('uuid'); -const systemConfigurationService = require('./system-configuration-service'); -const config = require('../config/config'); - -const { PropertyNotAllowedError, InvalidTypeError } = require('../exceptions'); - -const BaseService = require('./_base.service'); -const softwareRepository = require('../repository/software-repository'); - -const { Malware: MalwareType, Tool: ToolType } = require('../lib/types'); - -class SoftwareService extends BaseService { - async create(data, options) { - // This function handles two use cases: - // 1. This is a completely new object. Create a new object and generate the stix.id if not already - // provided. Set both stix.created_by_ref and stix.x_mitre_modified_by_ref to the organization identity. - // 2. This is a new version of an existing object. Create a new object with the specified id. - // Set stix.x_mitre_modified_by_ref to the organization identity. - - // is_family defaults to true for malware, not allowed for tools - if (data?.stix?.type !== MalwareType && data?.stix?.type !== ToolType) { - throw new InvalidTypeError(); - } - - if (data.stix && data.stix.type === MalwareType && typeof data.stix.is_family !== 'boolean') { - data.stix.is_family = true; - } else if (data.stix && data.stix.type === ToolType && data.stix.is_family !== undefined) { - throw new PropertyNotAllowedError(); - } - - options = options || {}; - if (!options.import) { - // Set the ATT&CK Spec Version - data.stix.x_mitre_attack_spec_version = - data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; - - // Record the user account that created the object - if (options.userAccountId) { - data.workspace.workflow.created_by_user_account = options.userAccountId; - } - - // Set the default marking definitions - await this.setDefaultMarkingDefinitionsForObject(data); - - // Get the organization identity - const organizationIdentityRef = - await systemConfigurationService.retrieveOrganizationIdentityRef(); - - // Check for an existing object - let existingObject; - if (data.stix.id) { - existingObject = await this.repository.retrieveOneById(data.stix.id); - } - - if (existingObject) { - // New version of an existing object - // Only set the x_mitre_modified_by_ref property - data.stix.x_mitre_modified_by_ref = organizationIdentityRef; - } else { - // New object - // Assign a new STIX id if not already provided - if (!data.stix.id) { - // const stixIdPrefix = getStixIdPrefixFromModel(this.model.modelName, data.stix.type); - data.stix.id = `${data.stix.type}--${uuid.v4()}`; - } - - // Set the created_by_ref and x_mitre_modified_by_ref properties - data.stix.created_by_ref = organizationIdentityRef; - data.stix.x_mitre_modified_by_ref = organizationIdentityRef; - } - } - return await this.repository.save(data); - } -} - -module.exports = new SoftwareService(null, softwareRepository); diff --git a/app/services/analytics-service.js b/app/services/stix/analytics-service.js similarity index 96% rename from app/services/analytics-service.js rename to app/services/stix/analytics-service.js index 24115fa4..13803210 100644 --- a/app/services/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -1,16 +1,16 @@ 'use strict'; -const analyticsRepository = require('../repository/analytics-repository'); -const dataComponentsRepository = require('../repository/data-components-repository'); -const BaseService = require('./_base.service'); -const { Analytic: AnalyticType } = require('../lib/types'); +const analyticsRepository = require('../../repository/analytics-repository'); +const dataComponentsRepository = require('../../repository/data-components-repository'); +const { BaseService } = require('../meta-classes'); +const { Analytic: AnalyticType } = require('../../lib/types'); const { createAttackExternalReference, removeAttackExternalReferences, -} = require('../lib/external-reference-builder'); -const EventBus = require('../lib/event-bus'); -const logger = require('../lib/logger'); -const { NotFoundError } = require('../exceptions'); +} = require('../../lib/external-reference-builder'); +const EventBus = require('../../lib/event-bus'); +const logger = require('../../lib/logger'); +const Exceptions = require('../../exceptions'); /** * Service for managing analytics @@ -220,7 +220,7 @@ class AnalyticsService extends BaseService { * @param {Object} data.stix - STIX properties * @param {Object} data.workspace - Workbench metadata * @param {Object} options - Creation options - * @throws {NotFoundError} If a referenced data component does not exist + * @throws {Exceptions.NotFoundError} If a referenced data component does not exist * @returns {Promise} */ // eslint-disable-next-line no-unused-vars @@ -247,7 +247,7 @@ class AnalyticsService extends BaseService { const dataComponent = await dataComponentsRepository.retrieveLatestByStixId(dataComponentId); if (!dataComponent) { - throw new NotFoundError({ + throw new Exceptions.NotFoundError({ objectType: 'x-mitre-data-component', objectId: dataComponentId, message: `Cannot create analytic: Referenced data component ${dataComponentId} does not exist`, @@ -311,7 +311,7 @@ class AnalyticsService extends BaseService { * @param {string} stixModified - Modified timestamp of the version being updated * @param {Object} data - Updated analytic data * @param {Object} existingDocument - The existing analytic document - * @throws {NotFoundError} If a referenced data component does not exist + * @throws {Exceptions.NotFoundError} If a referenced data component does not exist * @returns {Promise} */ async beforeUpdate(stixId, stixModified, data, existingDocument) { @@ -339,7 +339,7 @@ class AnalyticsService extends BaseService { const dataComponent = await dataComponentsRepository.retrieveLatestByStixId(dataComponentId); if (!dataComponent) { - throw new NotFoundError({ + throw new Exceptions.NotFoundError({ objectType: 'x-mitre-data-component', objectId: dataComponentId, message: `Cannot update analytic: Referenced data component ${dataComponentId} does not exist`, diff --git a/app/services/stix/assets-service.js b/app/services/stix/assets-service.js new file mode 100644 index 00000000..2eebe79a --- /dev/null +++ b/app/services/stix/assets-service.js @@ -0,0 +1,9 @@ +'use strict'; + +const assetsRepository = require('../../repository/assets-repository'); +const { BaseService } = require('../meta-classes'); +const { Asset: AssetType } = require('../../lib/types'); + +class AssetsService extends BaseService {} + +module.exports = new AssetsService(AssetType, assetsRepository); diff --git a/app/services/attack-objects-service.js b/app/services/stix/attack-objects-service.js similarity index 88% rename from app/services/attack-objects-service.js rename to app/services/stix/attack-objects-service.js index cb1efb1e..f08f073c 100644 --- a/app/services/attack-objects-service.js +++ b/app/services/stix/attack-objects-service.js @@ -1,11 +1,11 @@ 'use strict'; -const attackObjectsRepository = require('../repository/attack-objects-repository'); -const BaseService = require('./_base.service'); +const attackObjectsRepository = require('../../repository/attack-objects-repository'); +const { BaseService } = require('../meta-classes'); const identitiesService = require('./identities-service'); const relationshipsService = require('./relationships-service'); -const logger = require('../lib/logger'); -const { NotImplementedError, DatabaseError } = require('../exceptions'); +const logger = require('../../lib/logger'); +const { NotImplementedError, DatabaseError } = require('../../exceptions'); class AttackObjectsService extends BaseService { /** @@ -206,18 +206,18 @@ class AttackObjectsService extends BaseService { // Map STIX types to their repositories const repositoryMap = { - 'x-mitre-tactic': require('../repository/tactics-repository'), - 'attack-pattern': require('../repository/techniques-repository'), - 'intrusion-set': require('../repository/groups-repository'), - malware: require('../repository/software-repository'), - tool: require('../repository/software-repository'), - 'course-of-action': require('../repository/mitigations-repository'), - 'x-mitre-data-source': require('../repository/data-sources-repository'), - 'x-mitre-data-component': require('../repository/data-components-repository'), - 'x-mitre-asset': require('../repository/assets-repository'), - campaign: require('../repository/campaigns-repository'), - 'x-mitre-detection-strategy': require('../repository/detection-strategies-repository'), - 'x-mitre-analytic': require('../repository/analytics-repository'), + 'x-mitre-tactic': require('../../repository/tactics-repository'), + 'attack-pattern': require('../../repository/techniques-repository'), + 'intrusion-set': require('../../repository/groups-repository'), + malware: require('../../repository/software-repository'), + tool: require('../../repository/software-repository'), + 'course-of-action': require('../../repository/mitigations-repository'), + 'x-mitre-data-source': require('../../repository/data-sources-repository'), + 'x-mitre-data-component': require('../../repository/data-components-repository'), + 'x-mitre-asset': require('../../repository/assets-repository'), + campaign: require('../../repository/campaigns-repository'), + 'x-mitre-detection-strategy': require('../../repository/detection-strategies-repository'), + 'x-mitre-analytic': require('../../repository/analytics-repository'), }; const repository = repositoryMap[stixType]; diff --git a/app/services/stix/campaigns-service.js b/app/services/stix/campaigns-service.js new file mode 100644 index 00000000..4e504325 --- /dev/null +++ b/app/services/stix/campaigns-service.js @@ -0,0 +1,9 @@ +'use strict'; + +const campaignsRepository = require('../../repository/campaigns-repository'); +const { BaseService } = require('../meta-classes'); +const { Campaign: CampaignType } = require('../../lib/types'); + +class CampaignService extends BaseService {} + +module.exports = new CampaignService(CampaignType, campaignsRepository); diff --git a/app/services/collection-bundles-service/bundle-helpers.js b/app/services/stix/collection-bundles-service/bundle-helpers.js similarity index 100% rename from app/services/collection-bundles-service/bundle-helpers.js rename to app/services/stix/collection-bundles-service/bundle-helpers.js diff --git a/app/services/collection-bundles-service/export-bundle.js b/app/services/stix/collection-bundles-service/export-bundle.js similarity index 96% rename from app/services/collection-bundles-service/export-bundle.js rename to app/services/stix/collection-bundles-service/export-bundle.js index 81fb5312..270fd19b 100644 --- a/app/services/collection-bundles-service/export-bundle.js +++ b/app/services/stix/collection-bundles-service/export-bundle.js @@ -2,11 +2,11 @@ const uuid = require('uuid'); -const systemConfigurationService = require('../../services/system-configuration-service'); -const collectionsService = require('../../services/collections-service'); -const notesService = require('../../services/notes-service'); -const linkById = require('../../lib/linkById'); -const logger = require('../../lib/logger'); +const systemConfigurationService = require('../../system/system-configuration-service'); +const collectionsService = require('../collections-service'); +const notesService = require('../../system/notes-service'); +const linkById = require('../../../lib/linkById'); +const logger = require('../../../lib/logger'); const { errors } = require('./bundle-helpers'); diff --git a/app/services/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js similarity index 91% rename from app/services/collection-bundles-service/import-bundle.js rename to app/services/stix/collection-bundles-service/import-bundle.js index d58af922..2b406e19 100644 --- a/app/services/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -11,41 +11,40 @@ const { defaultAttackSpecVersion, toEpoch, } = require('./bundle-helpers'); -const { DuplicateIdError } = require('../../exceptions'); -const { Collection: CollectionType } = require('../../lib/types'); +const { DuplicateIdError } = require('../../../exceptions'); -const logger = require('../../lib/logger'); -const config = require('../../config/config'); -const types = require('../../lib/types'); +const logger = require('../../../lib/logger'); +const config = require('../../../config/config'); +const types = require('../../../lib/types'); -const collectionsService = require('../../services/collections-service'); -const referencesService = require('../../services/references-service'); +const collectionsService = require('../collections-service'); +const referencesService = require('../../system/references-service'); -const Collection = require('../../models/collection-model'); +const Collection = require('../../../models/collection-model'); // Service mapping object using the type constants const serviceMap = { - [types.Technique]: require('../../services/techniques-service'), - [types.Tactic]: require('../../services/tactics-service'), - [types.Group]: require('../../services/groups-service'), - [types.Campaign]: require('../../services/campaigns-service'), - [types.Mitigation]: require('../../services/mitigations-service'), - [types.Matrix]: require('../../services/matrices-service'), - [types.Relationship]: require('../../services/relationships-service'), - [types.MarkingDefinition]: require('../../services/marking-definitions-service'), - [types.Identity]: require('../../services/identities-service'), - [types.Note]: require('../../services/notes-service'), - [types.DataSource]: require('../../services/data-sources-service'), - [types.DataComponent]: require('../../services/data-components-service'), - [types.Asset]: require('../../services/assets-service'), - [types.Analytic]: require('../../services/analytics-service'), - [types.DetectionStrategy]: require('../../services/detection-strategies-service'), + [types.Technique]: require('../techniques-service'), + [types.Tactic]: require('../tactics-service'), + [types.Group]: require('../groups-service'), + [types.Campaign]: require('../campaigns-service'), + [types.Mitigation]: require('../mitigations-service'), + [types.Matrix]: require('../matrices-service'), + [types.Relationship]: require('../relationships-service'), + [types.MarkingDefinition]: require('../marking-definitions-service'), + [types.Identity]: require('../identities-service'), + [types.Note]: require('../../system/notes-service'), + [types.DataSource]: require('../data-sources-service'), + [types.DataComponent]: require('../data-components-service'), + [types.Asset]: require('../assets-service'), + [types.Analytic]: require('../analytics-service'), + [types.DetectionStrategy]: require('../detection-strategies-service'), }; // Handle special cases that share a service const softwareTypes = [types.Malware, types.Tool]; softwareTypes.forEach((type) => { - serviceMap[type] = require('../../services/software-service'); + serviceMap[type] = require('../software-service'); }); /** @@ -172,7 +171,7 @@ async function processStixObject( const service = getServiceForType(importObject.type); if (!service) { - if (importObject.type === CollectionType) { + if (importObject.type === types.Collection) { return; // Skip x-mitre-collection objects } @@ -274,7 +273,7 @@ async function processObjects( // Check if object is in x_mitre_contents if ( !contentsMap.delete(makeKeyFromObject(importObject)) && - importObject.type !== CollectionType + importObject.type !== types.Collection ) { const importError = { object_ref: importObject.id, diff --git a/app/services/collection-bundles-service/index.js b/app/services/stix/collection-bundles-service/index.js similarity index 100% rename from app/services/collection-bundles-service/index.js rename to app/services/stix/collection-bundles-service/index.js diff --git a/app/services/collection-bundles-service/validate-bundle.js b/app/services/stix/collection-bundles-service/validate-bundle.js similarity index 97% rename from app/services/collection-bundles-service/validate-bundle.js rename to app/services/stix/collection-bundles-service/validate-bundle.js index 793fb360..d8cb83f7 100644 --- a/app/services/collection-bundles-service/validate-bundle.js +++ b/app/services/stix/collection-bundles-service/validate-bundle.js @@ -1,7 +1,7 @@ 'use strict'; const semver = require('semver'); -const config = require('../../config/config'); +const config = require('../../../config/config'); // Constants for validation error types const { validationErrors, defaultAttackSpecVersion, makeKey } = require('./bundle-helpers'); diff --git a/app/services/collection-indexes-service.js b/app/services/stix/collection-indexes-service.js similarity index 83% rename from app/services/collection-indexes-service.js rename to app/services/stix/collection-indexes-service.js index 01009adf..69f5637a 100644 --- a/app/services/collection-indexes-service.js +++ b/app/services/stix/collection-indexes-service.js @@ -1,9 +1,9 @@ 'use strict'; -const CollectionIndexesRepository = require('../repository/collection-indexes-repository'); -const BaseService = require('./_base.service'); -const { MissingParameterError, DatabaseError } = require('../exceptions'); -const config = require('../config/config'); +const CollectionIndexesRepository = require('../../repository/collection-indexes-repository'); +const { BaseService } = require('../meta-classes'); +const { MissingParameterError, DatabaseError } = require('../../exceptions'); +const config = require('../../config/config'); class CollectionIndexesService extends BaseService { async retrieveAll(options) { diff --git a/app/services/collections-service.js b/app/services/stix/collections-service.js similarity index 97% rename from app/services/collections-service.js rename to app/services/stix/collections-service.js index 16c60c6b..b0d17f57 100644 --- a/app/services/collections-service.js +++ b/app/services/stix/collections-service.js @@ -1,8 +1,8 @@ 'use strict'; -const BaseService = require('./_base.service'); -const collectionRepository = require('../repository/collections-repository'); -const { Collection: CollectionType } = require('../lib/types'); +const { BaseService } = require('../meta-classes'); +const collectionRepository = require('../../repository/collections-repository'); +const { Collection: CollectionType } = require('../../lib/types'); const attackObjectsService = require('./attack-objects-service'); @@ -10,7 +10,7 @@ const { MissingParameterError, BadlyFormattedParameterError, InvalidQueryStringParameterError, -} = require('../exceptions'); +} = require('../../exceptions'); class CollectionsService extends BaseService { /** diff --git a/app/services/data-components-service.js b/app/services/stix/data-components-service.js similarity index 94% rename from app/services/data-components-service.js rename to app/services/stix/data-components-service.js index 9a8b5a97..93d41883 100644 --- a/app/services/data-components-service.js +++ b/app/services/stix/data-components-service.js @@ -1,10 +1,10 @@ 'use strict'; -const dataComponentsRepository = require('../repository/data-components-repository.js'); -const BaseService = require('./_base.service'); -const { DataComponent: DataComponentType } = require('../lib/types.js'); -const EventBus = require('../lib/event-bus'); -const logger = require('../lib/logger'); +const dataComponentsRepository = require('../../repository/data-components-repository'); +const { BaseService } = require('../meta-classes'); +const { DataComponent: DataComponentType } = require('../../lib/types'); +const EventBus = require('../../lib/event-bus'); +const logger = require('../../lib/logger'); /** * Service for managing data components diff --git a/app/services/data-sources-service.js b/app/services/stix/data-sources-service.js similarity index 88% rename from app/services/data-sources-service.js rename to app/services/stix/data-sources-service.js index ecf5479a..7dc8810f 100644 --- a/app/services/data-sources-service.js +++ b/app/services/stix/data-sources-service.js @@ -1,15 +1,15 @@ 'use strict'; -const dataSourcesRepository = require('../repository/data-sources-repository'); +const dataSourcesRepository = require('../../repository/data-sources-repository'); const identitiesService = require('./identities-service'); const dataComponentsService = require('./data-components-service'); -const BaseService = require('./_base.service'); -const { DataSource: DataSourceType } = require('../lib/types'); +const { BaseService } = require('../meta-classes'); +const { DataSource: DataSourceType } = require('../../lib/types'); const { MissingParameterError, BadlyFormattedParameterError, InvalidQueryStringParameterError, -} = require('../exceptions'); +} = require('../../exceptions'); class DataSourcesService extends BaseService { errors = { @@ -44,6 +44,10 @@ class DataSourcesService extends BaseService { includeRevoked: true, }); + console.log('DEBUG: allDataComponents type:', typeof allDataComponents); + console.log('DEBUG: allDataComponents is array:', Array.isArray(allDataComponents)); + console.log('DEBUG: allDataComponents:', JSON.stringify(allDataComponents, null, 2)); + // Add the data components that reference the data source dataSource.dataComponents = allDataComponents.filter( (dataComponent) => dataComponent.stix.x_mitre_data_source_ref === dataSource.stix.id, diff --git a/app/services/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js similarity index 94% rename from app/services/detection-strategies-service.js rename to app/services/stix/detection-strategies-service.js index e09d6f8e..b7ed36d7 100644 --- a/app/services/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -1,11 +1,11 @@ 'use strict'; -const detectionStrategiesRepository = require('../repository/detection-strategies-repository'); -const analyticsRepository = require('../repository/analytics-repository'); -const BaseService = require('./_base.service'); -const { DetectionStrategy: DetectionStrategyType } = require('../lib/types'); -const logger = require('../lib/logger'); -const EventBus = require('../lib/event-bus'); +const detectionStrategiesRepository = require('../../repository/detection-strategies-repository'); +const analyticsRepository = require('../../repository/analytics-repository'); +const { BaseService } = require('../meta-classes'); +const { DetectionStrategy: DetectionStrategyType } = require('../../lib/types'); +const logger = require('../../lib/logger'); +const EventBus = require('../../lib/event-bus'); /** * Service for managing detection strategies diff --git a/app/services/stix/groups-service.js b/app/services/stix/groups-service.js new file mode 100644 index 00000000..51610760 --- /dev/null +++ b/app/services/stix/groups-service.js @@ -0,0 +1,9 @@ +'use strict'; + +const { BaseService } = require('../meta-classes'); +const groupsRepository = require('../../repository/groups-repository'); +const { Group: GroupType } = require('../../lib/types'); + +class GroupsService extends BaseService {} + +module.exports = new GroupsService(GroupType, groupsRepository); diff --git a/app/services/identities-service.js b/app/services/stix/identities-service.js similarity index 82% rename from app/services/identities-service.js rename to app/services/stix/identities-service.js index a9df91a1..d2a02a00 100644 --- a/app/services/identities-service.js +++ b/app/services/stix/identities-service.js @@ -1,11 +1,11 @@ 'use strict'; const uuid = require('uuid'); -const config = require('../config/config'); -const identitiesRepository = require('../repository/identities-repository'); -const BaseService = require('./_base.service'); -const { InvalidTypeError } = require('../exceptions'); -const { Identity: IdentityType } = require('../lib/types'); +const config = require('../../config/config'); +const identitiesRepository = require('../../repository/identities-repository'); +const { BaseService } = require('../meta-classes'); +const { InvalidTypeError } = require('../../exceptions'); +const { Identity: IdentityType } = require('../../lib/types'); class IdentitiesService extends BaseService { /** diff --git a/app/services/marking-definitions-service.js b/app/services/stix/marking-definitions-service.js similarity index 94% rename from app/services/marking-definitions-service.js rename to app/services/stix/marking-definitions-service.js index be54a642..f7a56155 100644 --- a/app/services/marking-definitions-service.js +++ b/app/services/stix/marking-definitions-service.js @@ -1,16 +1,16 @@ 'use strict'; const uuid = require('uuid'); -const BaseService = require('./_base.service'); -const markingDefinitionsRepository = require('../repository/marking-definitions-repository'); -const { MarkingDefinition: MarkingDefinitionType } = require('../lib/types'); +const { BaseService } = require('../meta-classes'); +const markingDefinitionsRepository = require('../../repository/marking-definitions-repository'); +const { MarkingDefinition: MarkingDefinitionType } = require('../../lib/types'); const { MissingParameterError, BadlyFormattedParameterError, CannotUpdateStaticObjectError, DuplicateIdError, -} = require('../exceptions'); +} = require('../../exceptions'); // NOTE: A marking definition does not support the modified or revoked properties!! diff --git a/app/services/matrices-service.js b/app/services/stix/matrices-service.js similarity index 90% rename from app/services/matrices-service.js rename to app/services/stix/matrices-service.js index 5487f2e0..61024bd6 100644 --- a/app/services/matrices-service.js +++ b/app/services/stix/matrices-service.js @@ -1,11 +1,11 @@ 'use strict'; -const BaseService = require('./_base.service'); -const matrixRepository = require('../repository/matrix-repository'); -const { Matrix: MatrixType } = require('../lib/types'); +const { BaseService } = require('../meta-classes'); +const matrixRepository = require('../../repository/matrix-repository'); +const { Matrix: MatrixType } = require('../../lib/types'); const tacticsService = require('./tactics-service'); -const logger = require('../lib/logger'); -const { TacticsServiceError, MissingParameterError } = require('../exceptions'); +const logger = require('../../lib/logger'); +const { TacticsServiceError, MissingParameterError } = require('../../exceptions'); class MatrixService extends BaseService { constructor(type, repository) { diff --git a/app/services/stix/mitigations-service.js b/app/services/stix/mitigations-service.js new file mode 100644 index 00000000..dc1ce7de --- /dev/null +++ b/app/services/stix/mitigations-service.js @@ -0,0 +1,9 @@ +'use strict'; + +const mitigationsRepository = require('../../repository/mitigations-repository'); +const { BaseService } = require('../meta-classes'); +const { Mitigation: MitigationType } = require('../../lib/types'); + +class MitigationsService extends BaseService {} + +module.exports = new MitigationsService(MitigationType, mitigationsRepository); diff --git a/app/services/relationships-service.js b/app/services/stix/relationships-service.js similarity index 93% rename from app/services/relationships-service.js rename to app/services/stix/relationships-service.js index 7df71a5b..68b356c6 100644 --- a/app/services/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -1,8 +1,8 @@ 'use strict'; -const BaseService = require('./_base.service'); -const relationshipsRepository = require('../repository/relationships-repository'); -const { Relationship: RelationshipType } = require('../lib/types'); +const { BaseService } = require('../meta-classes'); +const relationshipsRepository = require('../../repository/relationships-repository'); +const { Relationship: RelationshipType } = require('../../lib/types'); // Map STIX types to ATT&CK types const objectTypeMap = new Map([ diff --git a/app/services/stix/software-service.js b/app/services/stix/software-service.js new file mode 100644 index 00000000..5d10441e --- /dev/null +++ b/app/services/stix/software-service.js @@ -0,0 +1,57 @@ +'use strict'; + +const { PropertyNotAllowedError } = require('../../exceptions'); + +const { BaseService } = require('../meta-classes'); +const softwareRepository = require('../../repository/software-repository'); + +const { Malware: MalwareType, Tool: ToolType } = require('../../lib/types'); + +class SoftwareService extends BaseService { + /** + * Set domain-specific defaults before creating a software object. + * - For malware: `is_family` defaults to true + * - For tools: `is_family` is not allowed + * + * @param {Object} data - The software object data + * @param {Object} _options - Creation options (unused) + */ + // eslint-disable-next-line no-unused-vars + async beforeCreate(data, _options) { + // Set is_family default for malware + if (data.stix && data.stix.type === MalwareType && typeof data.stix.is_family !== 'boolean') { + data.stix.is_family = true; + } + // Validate that is_family is not set for tools + else if (data.stix && data.stix.type === ToolType && data.stix.is_family !== undefined) { + throw new PropertyNotAllowedError('is_family is not allowed for tool objects'); + } + } + + /** + * Override create to handle type validation for multiple types (malware and tool). + * SoftwareService handles both 'malware' and 'tool' types, so we need custom validation. + * We temporarily set this.type to match the incoming data type so BaseService validation passes. + */ + async create(data, options) { + // Validate that the type is either malware or tool + if (data?.stix?.type !== MalwareType && data?.stix?.type !== ToolType) { + const { InvalidTypeError } = require('../../exceptions'); + throw new InvalidTypeError(); + } + + // Temporarily set this.type to the incoming type for BaseService validation + const originalType = this.type; + this.type = data.stix.type; + + try { + // Call parent create which will trigger beforeCreate hook + return await super.create(data, options); + } finally { + // Restore original type + this.type = originalType; + } + } +} + +module.exports = new SoftwareService(null, softwareRepository); diff --git a/app/services/stix-bundles-service-old.js b/app/services/stix/stix-bundles-service-old.js similarity index 97% rename from app/services/stix-bundles-service-old.js rename to app/services/stix/stix-bundles-service-old.js index 23a2d152..9acae2b3 100644 --- a/app/services/stix-bundles-service-old.js +++ b/app/services/stix/stix-bundles-service-old.js @@ -1,23 +1,23 @@ 'use strict'; const uuid = require('uuid'); -const config = require('../config/config'); -const BaseService = require('./_base.service'); -const linkById = require('../lib/linkById'); -const logger = require('../lib/logger'); +const config = require('../../config/config'); +const { BaseService } = require('../meta-classes'); +const linkById = require('../../lib/linkById'); +const logger = require('../../lib/logger'); // Import repositories -const attackObjectsRepository = require('../repository/attack-objects-repository'); -const matrixRepository = require('../repository/matrix-repository'); -const mitigationsRepository = require('../repository/mitigations-repository'); -const notesRepository = require('../repository/notes-repository'); -const relationshipsRepository = require('../repository/relationships-repository'); -const softwareRepository = require('../repository/software-repository'); -const tacticsRepository = require('../repository/tactics-repository'); -const techniquesRepository = require('../repository/techniques-repository'); +const attackObjectsRepository = require('../../repository/attack-objects-repository'); +const matrixRepository = require('../../repository/matrix-repository'); +const mitigationsRepository = require('../../repository/mitigations-repository'); +const notesRepository = require('../../repository/notes-repository'); +const relationshipsRepository = require('../../repository/relationships-repository'); +const softwareRepository = require('../../repository/software-repository'); +const tacticsRepository = require('../../repository/tactics-repository'); +const techniquesRepository = require('../../repository/techniques-repository'); // Import services -const notesService = require('./notes-service'); +const notesService = require('../system/notes-service'); /** * Service for generating STIX bundles from the ATT&CK database. diff --git a/app/services/stix-bundles-service.js b/app/services/stix/stix-bundles-service.js similarity index 96% rename from app/services/stix-bundles-service.js rename to app/services/stix/stix-bundles-service.js index cca24054..f986b4cb 100644 --- a/app/services/stix-bundles-service.js +++ b/app/services/stix/stix-bundles-service.js @@ -1,28 +1,28 @@ 'use strict'; const uuid = require('uuid'); -const config = require('../config/config'); -const BaseService = require('./_base.service'); -const linkById = require('../lib/linkById'); -const logger = require('../lib/logger'); -const { requiresAttackId } = require('../lib/attack-id-generator'); +const config = require('../../config/config'); +const { BaseService } = require('../meta-classes'); +const linkById = require('../../lib/linkById'); +const logger = require('../../lib/logger'); +const { requiresAttackId } = require('../../lib/attack-id-generator'); // Import repositories -const analyticsRepository = require('../repository/analytics-repository'); -const attackObjectsRepository = require('../repository/attack-objects-repository'); -const matrixRepository = require('../repository/matrix-repository'); -const mitigationsRepository = require('../repository/mitigations-repository'); -const notesRepository = require('../repository/notes-repository'); -const relationshipsRepository = require('../repository/relationships-repository'); -const softwareRepository = require('../repository/software-repository'); -const tacticsRepository = require('../repository/tactics-repository'); -const techniquesRepository = require('../repository/techniques-repository'); -const dataComponentsRepository = require('../repository/data-components-repository'); -const dataSourcesRepository = require('../repository/data-sources-repository'); -const detectionStrategiesRepository = require('../repository/detection-strategies-repository'); +const analyticsRepository = require('../../repository/analytics-repository'); +const attackObjectsRepository = require('../../repository/attack-objects-repository'); +const matrixRepository = require('../../repository/matrix-repository'); +const mitigationsRepository = require('../../repository/mitigations-repository'); +const notesRepository = require('../../repository/notes-repository'); +const relationshipsRepository = require('../../repository/relationships-repository'); +const softwareRepository = require('../../repository/software-repository'); +const tacticsRepository = require('../../repository/tactics-repository'); +const techniquesRepository = require('../../repository/techniques-repository'); +const dataComponentsRepository = require('../../repository/data-components-repository'); +const dataSourcesRepository = require('../../repository/data-sources-repository'); +const detectionStrategiesRepository = require('../../repository/detection-strategies-repository'); // Import services -const notesService = require('./notes-service'); +const notesService = require('../system/notes-service'); /** * Service for generating STIX bundles from the ATT&CK database. diff --git a/app/services/tactics-service.js b/app/services/stix/tactics-service.js similarity index 91% rename from app/services/tactics-service.js rename to app/services/stix/tactics-service.js index 3b9ae7d8..296db4af 100644 --- a/app/services/tactics-service.js +++ b/app/services/stix/tactics-service.js @@ -1,11 +1,11 @@ 'use strict'; -const config = require('../config/config'); -const BaseService = require('./_base.service'); -const tacticsRepository = require('../repository/tactics-repository'); -const { Tactic: TacticType } = require('../lib/types'); +const config = require('../../config/config'); +const { BaseService } = require('../meta-classes'); +const tacticsRepository = require('../../repository/tactics-repository'); +const { Tactic: TacticType } = require('../../lib/types'); const techniquesService = require('./techniques-service'); -const { BadlyFormattedParameterError, MissingParameterError } = require('../exceptions'); +const { BadlyFormattedParameterError, MissingParameterError } = require('../../exceptions'); class TacticsService extends BaseService { static techniquesService = null; diff --git a/app/services/techniques-service.js b/app/services/stix/techniques-service.js similarity index 91% rename from app/services/techniques-service.js rename to app/services/stix/techniques-service.js index f928385d..023f8885 100644 --- a/app/services/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -1,11 +1,11 @@ 'use strict'; -const config = require('../config/config'); -const BaseService = require('./_base.service'); -const techniquesRepository = require('../repository/techniques-repository'); +const config = require('../../config/config'); +const { BaseService } = require('../meta-classes'); +const techniquesRepository = require('../../repository/techniques-repository'); -const { Technique: TechniqueType } = require('../lib/types'); -const { BadlyFormattedParameterError, MissingParameterError } = require('../exceptions'); +const { Technique: TechniqueType } = require('../../lib/types'); +const { BadlyFormattedParameterError, MissingParameterError } = require('../../exceptions'); class TechniquesService extends BaseService { static tacticsService = null; diff --git a/app/services/authentication-service.js b/app/services/system/authentication-service.js similarity index 98% rename from app/services/authentication-service.js rename to app/services/system/authentication-service.js index aceec2b6..8e5ee057 100644 --- a/app/services/authentication-service.js +++ b/app/services/system/authentication-service.js @@ -4,8 +4,8 @@ const request = require('superagent'); const crypto = require('crypto'); const jwtDecoder = require('jwt-decode'); -const logger = require('../lib/logger'); -const config = require('../config/config'); +const logger = require('../../lib/logger'); +const config = require('../../config/config'); const errors = { serviceNotFound: 'Service not found', diff --git a/app/services/notes-service.js b/app/services/system/notes-service.js similarity index 91% rename from app/services/notes-service.js rename to app/services/system/notes-service.js index ce629d2a..c2c74c00 100644 --- a/app/services/notes-service.js +++ b/app/services/system/notes-service.js @@ -1,15 +1,15 @@ 'use strict'; const _ = require('lodash'); -const notesRepository = require('../repository/notes-repository'); -const BaseService = require('./_base.service'); -const { Note: NoteType } = require('../lib/types'); +const notesRepository = require('../../repository/notes-repository'); +const { BaseService } = require('../meta-classes'); +const { Note: NoteType } = require('../../lib/types'); const { BadlyFormattedParameterError, DuplicateIdError, MissingParameterError, -} = require('../exceptions'); +} = require('../../exceptions'); class NotesService extends BaseService { async updateVersion(stixId, stixModified, data) { diff --git a/app/services/recent-activity-service.js b/app/services/system/recent-activity-service.js similarity index 92% rename from app/services/recent-activity-service.js rename to app/services/system/recent-activity-service.js index 7dc92647..41d96053 100644 --- a/app/services/recent-activity-service.js +++ b/app/services/system/recent-activity-service.js @@ -1,7 +1,7 @@ 'use strict'; -const recentActivityRepository = require('../repository/recent-activity-repository'); -const identitiesService = require('./identities-service'); +const recentActivityRepository = require('../../repository/recent-activity-repository'); +const identitiesService = require('../stix/identities-service'); class RecentActivityService { constructor(repository) { diff --git a/app/services/references-service.js b/app/services/system/references-service.js similarity index 81% rename from app/services/references-service.js rename to app/services/system/references-service.js index 2b169744..a6e1f2ea 100644 --- a/app/services/references-service.js +++ b/app/services/system/references-service.js @@ -1,8 +1,8 @@ 'use strict'; -const BaseService = require('./_base.service'); -const referencesRepository = require('../repository/references-repository'); -const { MissingParameterError } = require('../exceptions'); +const { BaseService } = require('../meta-classes'); +const referencesRepository = require('../../repository/references-repository'); +const { MissingParameterError } = require('../../exceptions'); class ReferencesService { constructor(repository) { diff --git a/app/services/system-configuration-service.js b/app/services/system/system-configuration-service.js similarity index 95% rename from app/services/system-configuration-service.js rename to app/services/system/system-configuration-service.js index d66bf3bd..7d8362c3 100644 --- a/app/services/system-configuration-service.js +++ b/app/services/system/system-configuration-service.js @@ -1,12 +1,12 @@ 'use strict'; const fs = require('fs'); -const config = require('../config/config'); -const systemConfigurationRepository = require('../repository/system-configurations-repository'); +const config = require('../../config/config'); +const systemConfigurationRepository = require('../../repository/system-configurations-repository'); const userAccountsService = require('./user-accounts-service'); -const identitiesService = require('./identities-service'); -const markingDefinitionsService = require('./marking-definitions-service'); -const BaseService = require('./_base.service'); +const identitiesService = require('../stix/identities-service'); +const markingDefinitionsService = require('../stix/marking-definitions-service'); +const { BaseService } = require('../meta-classes'); const { SystemConfigurationNotFound, OrganizationIdentityNotSetError, @@ -15,7 +15,7 @@ const { AnonymousUserAccountNotSetError, AnonymousUserAccountNotFoundError, NotImplementedError, -} = require('../exceptions'); +} = require('../../exceptions'); class SystemConfigurationService extends BaseService { constructor() { diff --git a/app/services/teams-service.js b/app/services/system/teams-service.js similarity index 95% rename from app/services/teams-service.js rename to app/services/system/teams-service.js index e1c183bb..4fcc6d02 100644 --- a/app/services/teams-service.js +++ b/app/services/system/teams-service.js @@ -1,15 +1,15 @@ 'use strict'; -const regexValidator = require('../lib/regex'); -const UserAccount = require('../models/user-account-model'); +const regexValidator = require('../../lib/regex'); +const UserAccount = require('../../models/user-account-model'); const userAccountsService = require('./user-accounts-service'); const { MissingParameterError, BadlyFormattedParameterError, NotFoundError, DuplicateIdError, -} = require('../exceptions'); -const teamsRepository = require('../repository/teams-repository'); +} = require('../../exceptions'); +const teamsRepository = require('../../repository/teams-repository'); const uuid = require('uuid'); class TeamsService { diff --git a/app/services/user-accounts-service.js b/app/services/system/user-accounts-service.js similarity index 86% rename from app/services/user-accounts-service.js rename to app/services/system/user-accounts-service.js index 5108cc85..4c198dcb 100644 --- a/app/services/user-accounts-service.js +++ b/app/services/system/user-accounts-service.js @@ -1,15 +1,10 @@ 'use strict'; const uuid = require('uuid'); -const logger = require('../lib/logger'); -const TeamsRepository = require('../repository/teams-repository'); -const UserAccountsRepository = require('../repository/user-accounts-repository'); -const { - MissingParameterError, - BadlyFormattedParameterError, - DuplicateIdError, - DuplicateEmailError, -} = require('../exceptions'); +const logger = require('../../lib/logger'); +const teamsRepository = require('../../repository/teams-repository'); +const userAccountsRepository = require('../../repository/user-accounts-repository'); +const Exceptions = require('../../exceptions'); class UserAccountsService { constructor(type, repository) { @@ -70,7 +65,7 @@ class UserAccountsService { async retrieveById(userAccountId, options) { try { if (!userAccountId) { - throw new MissingParameterError('userAccountId'); + throw new Exceptions.MissingParameterError('userAccountId'); } const userAccount = await this.repository.retrieveOneById(userAccountId); @@ -87,7 +82,7 @@ class UserAccountsService { return userAccount; } catch (err) { if (err.name === 'CastError') { - throw new BadlyFormattedParameterError('userId'); + throw new Exceptions.BadlyFormattedParameterError('userId'); } else { throw err; } @@ -96,7 +91,7 @@ class UserAccountsService { async retrieveByEmail(email) { if (!email) { - throw new MissingParameterError('email'); + throw new Exceptions.MissingParameterError('email'); } try { @@ -106,7 +101,7 @@ class UserAccountsService { return userAccount; } catch (err) { if (err.name === 'CastError') { - throw new BadlyFormattedParameterError('email'); + throw new Exceptions.BadlyFormattedParameterError('email'); } else { throw err; } @@ -123,7 +118,7 @@ class UserAccountsService { // error if it occurs. const userAccount = await this.repository.retrieveOneByEmail(data.email); if (userAccount) { - throw new DuplicateEmailError(); + throw new Exceptions.DuplicateIdError(); } } @@ -155,7 +150,7 @@ class UserAccountsService { return savedUserAccount; } catch (err) { if (err.name === 'MongoServerError' && err.code === 11000) { - throw new DuplicateIdError(); + throw new Exceptions.DuplicateIdError(); } else { throw err; } @@ -164,7 +159,7 @@ class UserAccountsService { async updateFull(userAccountId, data) { if (!userAccountId) { - throw new MissingParameterError('userAccountId'); + throw new Exceptions.MissingParameterError('userAccountId'); } return await this.repository.updateById(userAccountId, data); @@ -172,7 +167,7 @@ class UserAccountsService { async delete(userAccountId) { if (!userAccountId) { - throw new MissingParameterError('userAccountId'); + throw new Exceptions.MissingParameterError('userAccountId'); } const userAccount = await this.repository.findOneAndDelete(userAccountId); @@ -207,10 +202,10 @@ class UserAccountsService { static async retrieveTeamsByUserId(userAccountId, options) { if (!userAccountId) { - throw new MissingParameterError('userAccountId'); + throw new Exceptions.MissingParameterError('userAccountId'); } - const results = await TeamsRepository.retrieveByUserId(userAccountId, options); + const results = await teamsRepository.retrieveByUserId(userAccountId, options); const teams = results[0].documents; if (options.includePagination) { @@ -236,4 +231,4 @@ class UserAccountsService { } } -module.exports = new UserAccountsService(null, UserAccountsRepository); +module.exports = new UserAccountsService(null, userAccountsRepository); From 1df8bb418c8bc838cb967921c8868d06ea84a600 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:55:17 -0500 Subject: [PATCH 105/370] refactor(models): remove middleware for propagating external_id to workspace.attack_id automatically --- app/models/attack-object-model.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/app/models/attack-object-model.js b/app/models/attack-object-model.js index a338c6df..c91f274d 100644 --- a/app/models/attack-object-model.js +++ b/app/models/attack-object-model.js @@ -4,8 +4,6 @@ const mongoose = require('mongoose'); const workspaceDefinitions = require('./subschemas/workspace'); const stixCoreDefinitions = require('./subschemas/stix-core'); -const config = require('../config/config'); - // Create the definition const attackObjectDefinition = { workspace: { @@ -23,18 +21,10 @@ const options = { }; const attackObjectSchema = new mongoose.Schema(attackObjectDefinition, options); -//Save the ATT&CK ID in a more easily queried location -attackObjectSchema.pre('save', function (next) { - if (this.stix.external_references) { - const mitreAttackReference = this.stix.external_references.find((externalReference) => - config.attackSourceNames.includes(externalReference.source_name), - ); - if (mitreAttackReference && mitreAttackReference.external_id) { - this.workspace.attack_id = mitreAttackReference.external_id; - } - } - return next(); -}); +// Note: workspace.attack_id is now managed by the service layer (BaseService) +// and external_references are built from workspace.attack_id, not the other way around. +// The pre-save hook that used to extract attack_id from external_references has been removed +// to avoid circular dependencies with the new external reference builder. // Add an index on stix.id and stix.modified // This improves the efficiency of queries and enforces uniqueness on this combination of properties From e128bb9b15029cca0fa78c3796ced42f00da5ee4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:56:11 -0500 Subject: [PATCH 106/370] fix(attack-id-generator): use config.attackSourceNames as search criteria --- app/lib/attack-id-generator.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js index aca90df0..38aca2c7 100644 --- a/app/lib/attack-id-generator.js +++ b/app/lib/attack-id-generator.js @@ -7,6 +7,7 @@ const { } = require('@mitre-attack/attack-data-model'); const { InvalidTypeError, DuplicateIdError } = require('../exceptions'); const logger = require('./logger'); +const config = require('../config/config'); /** * Determines if a given ATT&CK object type requires an ATT&CK ID. @@ -40,9 +41,9 @@ function extractAttackIdFromExternalReferences(stix) { return null; } - const attackSources = ['enterprise-attack', 'mobile-attack', 'ics-attack']; const attackRef = stix.external_references.find( - (ref) => ref.source_name && attackSources.includes(ref.source_name) && ref.external_id, + (ref) => + ref.source_name && config.attackSourceNames.includes(ref.source_name) && ref.external_id, ); return attackRef ? attackRef.external_id : null; From 03736dcf27696369ce89669b7ec7c2504010cdc7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:56:56 -0500 Subject: [PATCH 107/370] build(npm): add test:file script for executing individual spec.js files --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9cdd8bb3..eb38035e 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "test:openapi": "mocha --timeout 10000 ./app/tests/openapi", "test:authn": "./app/tests/run-mocha-separate-jobs.sh ./app/tests/authn", "test:fuzz": "mocha --timeout 10000 --recursive ./app/tests/fuzz", - "test:scheduler": "mocha --timeout 60000 --recursive ./app/tests/scheduler" + "test:scheduler": "mocha --timeout 60000 --recursive ./app/tests/scheduler", + "test:file": "mocha --timeout 10000" }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", From 4b87d6102a867500699dd082d4bcbf965d521bea Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:02:22 -0500 Subject: [PATCH 108/370] test: start updating regression tests to reflect new workflows --- .../analytics/analytics-includeRefs.spec.js | 16 +- .../analytics/analytics-pagination.spec.js | 10 +- app/tests/api/analytics/analytics-spec.js | 20 +- app/tests/api/assets/assets.spec.js | 16 +- .../attack-objects-attack-id.spec.js | 288 ------------------ .../attack-objects-pagination.spec.js | 2 +- .../api/attack-objects/attack-objects.spec.js | 2 +- app/tests/api/campaigns/campaigns.spec.js | 21 +- app/tests/api/collections/collections.spec.js | 7 +- .../data-components-pagination.spec.js | 2 +- .../data-components/data-components.spec.js | 12 +- .../data-sources-pagination.spec.js | 2 +- .../api/data-sources/data-sources.spec.js | 20 +- .../detection-strategies-pagination.spec.js | 2 +- .../detection-strategies-spec.js | 12 +- .../api/groups/groups-pagination.spec.js | 2 +- app/tests/api/groups/groups.query.spec.js | 4 +- app/tests/api/groups/groups.spec.js | 23 +- app/tests/api/identities/identities.spec.js | 12 +- app/tests/api/matrices/matrices.spec.js | 14 +- .../mitigations-pagination.spec.js | 2 +- app/tests/api/mitigations/mitigations.spec.js | 12 +- app/tests/api/notes/notes.spec.js | 17 +- .../recent-activity/recent-activity.spec.js | 2 +- .../relationships-pagination.spec.js | 2 +- .../api/relationships/relationships.spec.js | 12 +- .../api/software/software-pagination.spec.js | 2 +- app/tests/api/software/software.spec.js | 11 +- .../create-object-identity.spec.js | 7 +- app/tests/api/tactics/tactics.spec.js | 12 +- .../api/tactics/tactics.techniques.spec.js | 2 +- .../techniques/techniques-pagination.spec.js | 2 +- .../api/techniques/techniques.query.spec.js | 2 +- app/tests/api/techniques/techniques.spec.js | 12 +- .../api/techniques/techniques.tactics.spec.js | 2 +- .../api/user-accounts/user-accounts.spec.js | 2 +- app/tests/config/config.spec.js | 2 +- app/tests/shared/clone-for-create.js | 54 ++++ 38 files changed, 145 insertions(+), 499 deletions(-) delete mode 100644 app/tests/api/attack-objects/attack-objects-attack-id.spec.js create mode 100644 app/tests/shared/clone-for-create.js diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index 8dd3487e..ae345057 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -20,13 +20,7 @@ const analyticData = { name: 'test-analytic-with-refs', spec_version: '2.1', type: 'x-mitre-analytic', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'AN0001', - url: 'https://attack.mitre.org/analytics/AN0001', - }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', @@ -54,13 +48,7 @@ const detectionStrategyData = { name: 'test-detection-strategy', spec_version: '2.1', type: 'x-mitre-detection-strategy', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'DS0001', - url: 'https://attack.mitre.org/datasources/DS0001', - }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', diff --git a/app/tests/api/analytics/analytics-pagination.spec.js b/app/tests/api/analytics/analytics-pagination.spec.js index ebdab8fd..df01ba8e 100644 --- a/app/tests/api/analytics/analytics-pagination.spec.js +++ b/app/tests/api/analytics/analytics-pagination.spec.js @@ -1,4 +1,4 @@ -const analyticsService = require('../../../services/analytics-service'); +const analyticsService = require('../../../services/stix/analytics-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API @@ -13,13 +13,7 @@ const initialObjectData = { name: 'analytic-1', spec_version: '2.1', type: 'x-mitre-analytic', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'AN9999', - url: 'https://attack.mitre.org/analytics/AN9999', - }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', diff --git a/app/tests/api/analytics/analytics-spec.js b/app/tests/api/analytics/analytics-spec.js index db353dde..10ad394b 100644 --- a/app/tests/api/analytics/analytics-spec.js +++ b/app/tests/api/analytics/analytics-spec.js @@ -7,6 +7,7 @@ const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -207,12 +208,7 @@ describe('Analytics API', function () { }); it('POST /api/analytics does not create a analytic with the same id and modified date', async function () { - const body = _.cloneDeep(analytic1); - // Remove system-controlled fields - delete body._id; - delete body.__t; - delete body.__v; - delete body.workspace.attack_id; + const body = cloneForCreate(analytic1); await request(app) .post('/api/analytics') .send(body) @@ -223,11 +219,7 @@ describe('Analytics API', function () { let analytic2; it('POST /api/analytics should create a new version of a analytic with a duplicate stix.id but different stix.modified date', async function () { - const body = _.cloneDeep(analytic1); - body._id = undefined; - body.__t = undefined; - body.__v = undefined; - delete body.workspace.attack_id; + const body = cloneForCreate(analytic1); const timestamp = new Date().toISOString(); body.stix.modified = timestamp; const res = await request(app) @@ -245,11 +237,7 @@ describe('Analytics API', function () { let analytic3; it('POST /api/analytics should create a new version of a analytic with a duplicate stix.id but different stix.modified date', async function () { - const body = _.cloneDeep(analytic1); - body._id = undefined; - body.__t = undefined; - body.__v = undefined; - delete body.workspace.attack_id; + const body = cloneForCreate(analytic1); const timestamp = new Date().toISOString(); body.stix.modified = timestamp; const res = await request(app) diff --git a/app/tests/api/assets/assets.spec.js b/app/tests/api/assets/assets.spec.js index 478b44b1..ae990e99 100644 --- a/app/tests/api/assets/assets.spec.js +++ b/app/tests/api/assets/assets.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -211,7 +211,9 @@ describe('Assets API', function () { }); it('POST /api/assets does not create an asset with the same id and modified date', async function () { - const body = asset1; + const body = cloneForCreate(asset1); + // Keep the same modified date to trigger duplicate detection + body.stix.modified = asset1.stix.modified; await request(app) .post('/api/assets') .send(body) @@ -222,10 +224,7 @@ describe('Assets API', function () { let asset2; it('POST /api/assets should create a new version of an asset with a duplicate stix.id but different stix.modified date', async function () { - asset2 = _.cloneDeep(asset1); - asset2._id = undefined; - asset2.__t = undefined; - asset2.__v = undefined; + asset2 = cloneForCreate(asset1); const timestamp = new Date().toISOString(); asset2.stix.modified = timestamp; const body = asset2; @@ -244,10 +243,7 @@ describe('Assets API', function () { let asset3; it('POST /api/assets should create a new version of an asset with a duplicate stix.id but different stix.modified date', async function () { - asset3 = _.cloneDeep(asset1); - asset3._id = undefined; - asset3.__t = undefined; - asset3.__v = undefined; + asset3 = cloneForCreate(asset1); const timestamp = new Date().toISOString(); asset3.stix.modified = timestamp; const body = asset3; diff --git a/app/tests/api/attack-objects/attack-objects-attack-id.spec.js b/app/tests/api/attack-objects/attack-objects-attack-id.spec.js deleted file mode 100644 index 0117fd07..00000000 --- a/app/tests/api/attack-objects/attack-objects-attack-id.spec.js +++ /dev/null @@ -1,288 +0,0 @@ -const request = require('supertest'); -const { expect } = require('expect'); - -const database = require('../../../lib/database-in-memory'); -const databaseConfiguration = require('../../../lib/database-configuration'); -const AttackObject = require('../../../models/attack-object-model'); -// const config = require('../../../config/config'); -const login = require('../../shared/login'); -const tacticsService = require('../../../services/tactics-service'); -const techniquesService = require('../../../services/techniques-service'); -const groupsService = require('../../../services/groups-service'); - -const logger = require('../../../lib/logger'); -logger.level = 'debug'; - -describe('ATT&CK ID Generation API', function () { - let app; - let passportCookie; - - before(async function () { - // Establish the database connection - await database.initializeConnection(); - - // Wait until the indexes are created - await AttackObject.init(); - - // Check for a valid database configuration - await databaseConfiguration.checkSystemConfiguration(); - - // Disable ADM validation for tests - // TODO reenable after 'adm' branch merge - // config.validateRequests.withAttackDataModel = false; - // config.validateRequests.withOpenApi = true; - - // Initialize the express app - app = await require('../../../index').initializeApp(); - - // Log into the app - passportCookie = await login.loginAnonymous(app); - }); - - describe('GET /api/attack-objects/attack-id/next', function () { - it('should return 400 when type parameter is missing', async function () { - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(400); - - // OpenAPI validator catches this before controller - expect(res.text).toContain("must have required property 'type'"); - }); - - it('should return 400 for unsupported STIX type', async function () { - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'marking-definition' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(400); - - // OpenAPI validator validates enum values - expect(res.text).toContain('must be equal to one of the allowed values'); - }); - - it('should generate TA0001 for first tactic when database is empty', async function () { - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'x-mitre-tactic' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - expect(res.body).toHaveProperty('attack_id'); - expect(res.body.attack_id).toBe('TA0001'); - }); - - it('should generate T0001 for first technique when database is empty', async function () { - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'attack-pattern' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - expect(res.body).toHaveProperty('attack_id'); - expect(res.body.attack_id).toBe('T0001'); - }); - - it('should generate G0001 for first group when database is empty', async function () { - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'intrusion-set' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - expect(res.body).toHaveProperty('attack_id'); - expect(res.body.attack_id).toBe('G0001'); - }); - - it('should generate sequential IDs after creating objects', async function () { - // Create a tactic with TA0001 - const tactic = { - workspace: { - attack_id: 'TA0001', - workflow: { state: 'work-in-progress' }, - }, - stix: { - name: 'Test Tactic', - type: 'x-mitre-tactic', - spec_version: '2.1', - x_mitre_shortname: 'test-tactic', - description: 'A test tactic', - created: '2023-01-01T00:00:00.000Z', - modified: '2023-01-01T00:00:00.000Z', - x_mitre_domains: ['enterprise-attack'], - }, - }; - await tacticsService.create(tactic); - - // Request next tactic ID - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'x-mitre-tactic' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - expect(res.body.attack_id).toBe('TA0002'); - }); - - it('should handle gaps in ID sequence correctly', async function () { - // Create tactics with TA0002 and TA0005 (skipping TA0003 and TA0004) - const tactic2 = { - workspace: { - attack_id: 'TA0002', - workflow: { state: 'work-in-progress' }, - }, - stix: { - name: 'Test Tactic 2', - type: 'x-mitre-tactic', - spec_version: '2.1', - x_mitre_shortname: 'test-tactic-2', - description: 'A test tactic 2', - created: '2023-01-02T00:00:00.000Z', - modified: '2023-01-02T00:00:00.000Z', - x_mitre_domains: ['enterprise-attack'], - }, - }; - await tacticsService.create(tactic2); - - const tactic5 = { - workspace: { - attack_id: 'TA0005', - workflow: { state: 'work-in-progress' }, - }, - stix: { - name: 'Test Tactic 5', - type: 'x-mitre-tactic', - spec_version: '2.1', - x_mitre_shortname: 'test-tactic-5', - description: 'A test tactic 5', - created: '2023-01-05T00:00:00.000Z', - modified: '2023-01-05T00:00:00.000Z', - x_mitre_domains: ['enterprise-attack'], - }, - }; - await tacticsService.create(tactic5); - - // Next ID should be TA0006 (max + 1) - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'x-mitre-tactic' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - expect(res.body.attack_id).toBe('TA0006'); - }); - - it('should handle multiple types independently', async function () { - // Create a technique with T0001 - const technique = { - workspace: { - attack_id: 'T0001', - workflow: { state: 'work-in-progress' }, - }, - stix: { - name: 'Test Technique', - type: 'attack-pattern', - spec_version: '2.1', - description: 'A test technique', - created: '2023-01-01T00:00:00.000Z', - modified: '2023-01-01T00:00:00.000Z', - x_mitre_domains: ['enterprise-attack'], - x_mitre_is_subtechnique: false, - }, - }; - await techniquesService.create(technique); - - // Create a group with G0001 - const group = { - workspace: { - attack_id: 'G0001', - workflow: { state: 'work-in-progress' }, - }, - stix: { - name: 'Test Group', - type: 'intrusion-set', - spec_version: '2.1', - description: 'A test group', - created: '2023-01-01T00:00:00.000Z', - modified: '2023-01-01T00:00:00.000Z', - }, - }; - await groupsService.create(group); - - // Check that technique counter is T0002 - const techRes = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'attack-pattern' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200); - - expect(techRes.body.attack_id).toBe('T0002'); - - // Check that group counter is G0002 - const groupRes = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'intrusion-set' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200); - - expect(groupRes.body.attack_id).toBe('G0002'); - - // Check that tactic counter is still TA0006 (from previous test) - const tacticRes = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: 'x-mitre-tactic' }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200); - - expect(tacticRes.body.attack_id).toBe('TA0006'); - }); - - it('should generate IDs for all supported STIX types', async function () { - const typeToExpectedPrefix = { - 'x-mitre-tactic': 'TA', - 'attack-pattern': 'T', - 'intrusion-set': 'G', - malware: 'S', - tool: 'S', - 'course-of-action': 'M', - 'x-mitre-data-source': 'DS', - 'x-mitre-data-component': 'DC', - 'x-mitre-asset': 'A', - campaign: 'C', - 'x-mitre-detection-strategy': 'DET', - 'x-mitre-analytic': 'AN', - }; - - for (const [stixType, prefix] of Object.entries(typeToExpectedPrefix)) { - const res = await request(app) - .get('/api/attack-objects/attack-id/next') - .query({ type: stixType }) - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200); - - expect(res.body.attack_id).toMatch(new RegExp(`^${prefix}`)); - logger.debug(`${stixType} -> ${res.body.attack_id}`); - } - }); - }); - - after(async function () { - await database.closeConnection(); - }); -}); diff --git a/app/tests/api/attack-objects/attack-objects-pagination.spec.js b/app/tests/api/attack-objects/attack-objects-pagination.spec.js index 482cafb1..4a8cf0ff 100644 --- a/app/tests/api/attack-objects/attack-objects-pagination.spec.js +++ b/app/tests/api/attack-objects/attack-objects-pagination.spec.js @@ -1,4 +1,4 @@ -const techniquesService = require('../../../services/techniques-service'); +const techniquesService = require('../../../services/stix/techniques-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index 5c9ff8da..ef16c8e1 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -9,7 +9,7 @@ const AttackObject = require('../../../models/attack-object-model'); const login = require('../../shared/login'); const logger = require('../../../lib/logger'); -const collectionBundlesService = require('../../../services/collection-bundles-service'); +const collectionBundlesService = require('../../../services/stix/collection-bundles-service'); logger.level = 'debug'; // test malware object diff --git a/app/tests/api/campaigns/campaigns.spec.js b/app/tests/api/campaigns/campaigns.spec.js index c70970c4..9da85bf5 100644 --- a/app/tests/api/campaigns/campaigns.spec.js +++ b/app/tests/api/campaigns/campaigns.spec.js @@ -1,12 +1,13 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); + +const { cloneForCreate } = require('../../shared/clone-for-create'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const Campaign = require('../../../models/campaign-model'); -const markingDefinitionService = require('../../../services/marking-definitions-service'); -const systemConfigurationService = require('../../../services/system-configuration-service'); +const markingDefinitionService = require('../../../services/stix/marking-definitions-service'); +const systemConfigurationService = require('../../../services/system/system-configuration-service'); const config = require('../../../config/config'); const login = require('../../shared/login'); @@ -124,7 +125,7 @@ describe('Campaigns API', function () { const timestamp = new Date().toISOString(); initialObjectData.stix.created = timestamp; initialObjectData.stix.modified = timestamp; - const body = initialObjectData; + const body = cloneForCreate(initialObjectData); const res = await request(app) .post('/api/campaigns') .send(body) @@ -233,7 +234,7 @@ describe('Campaigns API', function () { }); it('POST /api/campaigns does not create a campaign with the same id and modified date', async function () { - const body = campaign1; + const body = cloneForCreate(campaign1); await request(app) .post('/api/campaigns') .send(body) @@ -249,10 +250,7 @@ describe('Campaigns API', function () { 'This is the second default marking definition'; defaultMarkingDefinition2 = await addDefaultMarkingDefinition(markingDefinitionData); - campaign2 = _.cloneDeep(campaign1); - campaign2._id = undefined; - campaign2.__t = undefined; - campaign2.__v = undefined; + campaign2 = cloneForCreate(campaign1); const timestamp = new Date().toISOString(); campaign2.stix.modified = timestamp; campaign2.stix.description = 'This is a new version of a campaign. Green.'; @@ -349,10 +347,7 @@ describe('Campaigns API', function () { let campaign3; it('POST /api/campaigns should create a new campaign with a different stix.id', async function () { - const campaign = _.cloneDeep(initialObjectData); - campaign._id = undefined; - campaign.__t = undefined; - campaign.__v = undefined; + const campaign = cloneForCreate(initialObjectData); campaign.stix.id = undefined; const timestamp = new Date().toISOString(); campaign.stix.created = timestamp; diff --git a/app/tests/api/collections/collections.spec.js b/app/tests/api/collections/collections.spec.js index 0e9e4e60..48602306 100644 --- a/app/tests/api/collections/collections.spec.js +++ b/app/tests/api/collections/collections.spec.js @@ -1,9 +1,9 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -362,10 +362,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { let collection2; it('POST /api/collections should create a new version of a collection with a duplicate stix.id but different stix.modified date', async function () { - const collection = _.cloneDeep(collection1); - collection._id = undefined; - collection.__t = undefined; - collection.__v = undefined; + const collection = cloneForCreate(collection1); const timestamp = new Date().toISOString(); collection.stix.modified = timestamp; diff --git a/app/tests/api/data-components/data-components-pagination.spec.js b/app/tests/api/data-components/data-components-pagination.spec.js index 04bfb107..2ebfc4d8 100644 --- a/app/tests/api/data-components/data-components-pagination.spec.js +++ b/app/tests/api/data-components/data-components-pagination.spec.js @@ -1,4 +1,4 @@ -const dataComponentsService = require('../../../services/data-components-service'); +const dataComponentsService = require('../../../services/stix/data-components-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index be3643c9..c006f3fe 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -261,10 +261,7 @@ describe('Data Components API', function () { let dataComponent2; it('POST /api/data-components should create a new version of a data component with a duplicate stix.id but different stix.modified date', async function () { - dataComponent2 = _.cloneDeep(dataComponent1); - dataComponent2._id = undefined; - dataComponent2.__t = undefined; - dataComponent2.__v = undefined; + dataComponent2 = cloneForCreate(dataComponent1); const timestamp = new Date().toISOString(); dataComponent2.stix.modified = timestamp; const body = dataComponent2; @@ -358,10 +355,7 @@ describe('Data Components API', function () { let dataComponent3; it('POST /api/data-components should create a new version of a data component with a duplicate stix.id but different stix.modified date', async function () { - dataComponent3 = _.cloneDeep(dataComponent1); - dataComponent3._id = undefined; - dataComponent3.__t = undefined; - dataComponent3.__v = undefined; + dataComponent3 = cloneForCreate(dataComponent1); const timestamp = new Date().toISOString(); dataComponent3.stix.modified = timestamp; const body = dataComponent3; diff --git a/app/tests/api/data-sources/data-sources-pagination.spec.js b/app/tests/api/data-sources/data-sources-pagination.spec.js index f9d1fb6d..436d283f 100644 --- a/app/tests/api/data-sources/data-sources-pagination.spec.js +++ b/app/tests/api/data-sources/data-sources-pagination.spec.js @@ -1,4 +1,4 @@ -const dataSourcesService = require('../../../services/data-sources-service'); +const dataSourcesService = require('../../../services/stix/data-sources-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index e2a07c00..107d21ed 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -2,13 +2,15 @@ const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); +const { cloneForCreate } = require('../../shared/clone-for-create'); + const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); -const dataComponentsService = require('../../../services/data-components-service'); +const dataComponentsService = require('../../../services/stix/data-components-service'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -26,7 +28,7 @@ const initialDataSourceData = { spec_version: '2.1', type: 'x-mitre-data-source', description: 'This is a data source.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', @@ -49,7 +51,7 @@ const initialDataComponentData = { spec_version: '2.1', type: 'x-mitre-data-component', description: 'This is a data component.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', @@ -259,7 +261,7 @@ describe('Data Sources API', function () { }); it('POST /api/data-sources does not create a data source with the same id and modified date', async function () { - const body = dataSource1; + const body = cloneForCreate(dataSource1); await request(app) .post('/api/data-sources') .send(body) @@ -270,10 +272,7 @@ describe('Data Sources API', function () { let dataSource2; it('POST /api/data-sources should create a new version of a data source with a duplicate stix.id but different stix.modified date', async function () { - dataSource2 = _.cloneDeep(dataSource1); - dataSource2._id = undefined; - dataSource2.__t = undefined; - dataSource2.__v = undefined; + dataSource2 = cloneForCreate(dataSource1); const timestamp = new Date().toISOString(); dataSource2.stix.modified = timestamp; const body = dataSource2; @@ -357,10 +356,7 @@ describe('Data Sources API', function () { let dataSource3; it('POST /api/data-sources should create a new version of a data source with a duplicate stix.id but different stix.modified date', async function () { - dataSource3 = _.cloneDeep(dataSource1); - dataSource3._id = undefined; - dataSource3.__t = undefined; - dataSource3.__v = undefined; + dataSource3 = cloneForCreate(dataSource1); const timestamp = new Date().toISOString(); dataSource3.stix.modified = timestamp; const body = dataSource3; diff --git a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js index a609cb8d..c91a7c9b 100644 --- a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js @@ -1,4 +1,4 @@ -const detectionStrategiesService = require('../../../services/detection-strategies-service'); +const detectionStrategiesService = require('../../../services/stix/detection-strategies-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index a873e902..d73e552c 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -209,10 +209,7 @@ describe('Detection Strategies API', function () { let detectionStrategy2; it('POST /api/detection-strategies should create a new version of a detection strategy with a duplicate stix.id but different stix.modified date', async function () { - detectionStrategy2 = _.cloneDeep(detectionStrategy1); - detectionStrategy2._id = undefined; - detectionStrategy2.__t = undefined; - detectionStrategy2.__v = undefined; + detectionStrategy2 = cloneForCreate(detectionStrategy1); const timestamp = new Date().toISOString(); detectionStrategy2.stix.modified = timestamp; const body = detectionStrategy2; @@ -231,10 +228,7 @@ describe('Detection Strategies API', function () { let detectionStrategy3; it('POST /api/detection-strategies should create a new version of a detection strategy with a duplicate stix.id but different stix.modified date', async function () { - detectionStrategy3 = _.cloneDeep(detectionStrategy1); - detectionStrategy3._id = undefined; - detectionStrategy3.__t = undefined; - detectionStrategy3.__v = undefined; + detectionStrategy3 = cloneForCreate(detectionStrategy1); const timestamp = new Date().toISOString(); detectionStrategy3.stix.modified = timestamp; const body = detectionStrategy3; diff --git a/app/tests/api/groups/groups-pagination.spec.js b/app/tests/api/groups/groups-pagination.spec.js index ee45c43a..5dcdf283 100644 --- a/app/tests/api/groups/groups-pagination.spec.js +++ b/app/tests/api/groups/groups-pagination.spec.js @@ -1,4 +1,4 @@ -const groupsService = require('../../../services/groups-service'); +const groupsService = require('../../../services/stix/groups-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 1c3aecb6..d12d17b5 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -13,8 +13,8 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const userAccountsService = require('../../../services/user-accounts-service'); -const groupsService = require('../../../services/groups-service'); +const userAccountsService = require('../../../services/system/user-accounts-service'); +const groupsService = require('../../../services/stix/groups-service'); function asyncWait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/app/tests/api/groups/groups.spec.js b/app/tests/api/groups/groups.spec.js index 515084b7..f44dba67 100644 --- a/app/tests/api/groups/groups.spec.js +++ b/app/tests/api/groups/groups.spec.js @@ -1,15 +1,15 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const Group = require('../../../models/group-model'); -const markingDefinitionService = require('../../../services/marking-definitions-service'); -const systemConfigurationService = require('../../../services/system-configuration-service'); +const markingDefinitionService = require('../../../services/stix/marking-definitions-service'); +const systemConfigurationService = require('../../../services/system/system-configuration-service'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -217,7 +217,7 @@ describe('Groups API', function () { }); it('POST /api/groups does not create a group with the same id and modified date', async function () { - const body = group1; + const body = cloneForCreate(group1); await request(app) .post('/api/groups') .send(body) @@ -233,10 +233,7 @@ describe('Groups API', function () { 'This is the second default marking definition'; defaultMarkingDefinition2 = await addDefaultMarkingDefinition(markingDefinitionData); - group2 = _.cloneDeep(group1); - group2._id = undefined; - group2.__t = undefined; - group2.__v = undefined; + group2 = cloneForCreate(group1); const timestamp = new Date().toISOString(); group2.stix.modified = timestamp; group2.stix.description = 'This is a new version of a group. Green.'; @@ -329,10 +326,7 @@ describe('Groups API', function () { let group3; it('POST /api/groups should create a new group with a different stix.id', async function () { - const group = _.cloneDeep(initialObjectData); - group._id = undefined; - group.__t = undefined; - group.__v = undefined; + const group = cloneForCreate(initialObjectData); group.stix.id = undefined; const timestamp = new Date().toISOString(); group.stix.created = timestamp; @@ -355,10 +349,7 @@ describe('Groups API', function () { let group4; it('POST /api/groups should create a new version of a group with a duplicate stix.id but different stix.modified date', async function () { - group4 = _.cloneDeep(group1); - group4._id = undefined; - group4.__t = undefined; - group4.__v = undefined; + group4 = cloneForCreate(group1); const timestamp = new Date().toISOString(); group4.stix.modified = timestamp; group4.stix.description = 'This is a new version of a group. Yellow.'; diff --git a/app/tests/api/identities/identities.spec.js b/app/tests/api/identities/identities.spec.js index ad9c9f3e..b7ee28d2 100644 --- a/app/tests/api/identities/identities.spec.js +++ b/app/tests/api/identities/identities.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -184,10 +184,7 @@ describe('Identity API', function () { let identity2; it('POST /api/identities should create a new version of an identity with a duplicate stix.id but different stix.modified date', async function () { - identity2 = _.cloneDeep(identity1); - identity2._id = undefined; - identity2.__t = undefined; - identity2.__v = undefined; + identity2 = cloneForCreate(identity1); const timestamp = new Date().toISOString(); identity2.stix.modified = timestamp; const body = identity2; @@ -271,10 +268,7 @@ describe('Identity API', function () { let identity3; it('POST /api/identities should create a new version of an identity with a duplicate stix.id but different stix.modified date', async function () { - identity3 = _.cloneDeep(identity1); - identity3._id = undefined; - identity3.__t = undefined; - identity3.__v = undefined; + identity3 = cloneForCreate(identity1); const timestamp = new Date().toISOString(); identity3.stix.modified = timestamp; const body = identity3; diff --git a/app/tests/api/matrices/matrices.spec.js b/app/tests/api/matrices/matrices.spec.js index 01ea2922..3f35a864 100644 --- a/app/tests/api/matrices/matrices.spec.js +++ b/app/tests/api/matrices/matrices.spec.js @@ -2,18 +2,18 @@ const fs = require('fs').promises; const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; -const collectionBundlesService = require('../../../services/collection-bundles-service'); +const collectionBundlesService = require('../../../services/stix/collection-bundles-service'); async function readJson(path) { const data = await fs.readFile(require.resolve(path)); @@ -201,10 +201,7 @@ describe('Matrices API', function () { let matrix2; it('POST /api/matrices should create a new version of a matrix with a duplicate stix.id but different stix.modified date', async function () { - matrix2 = _.cloneDeep(matrix1); - matrix2._id = undefined; - matrix2.__t = undefined; - matrix2.__v = undefined; + matrix2 = cloneForCreate(matrix1); const timestamp = new Date().toISOString(); matrix2.stix.modified = timestamp; const body = matrix2; @@ -224,10 +221,7 @@ describe('Matrices API', function () { let matrix3; it('POST /api/matrices should create a new version of a matrix with a duplicate stix.id but different stix.modified date', async function () { - matrix3 = _.cloneDeep(matrix1); - matrix3._id = undefined; - matrix3.__t = undefined; - matrix3.__v = undefined; + matrix3 = cloneForCreate(matrix1); const timestamp = new Date().toISOString(); matrix3.stix.modified = timestamp; const body = matrix3; diff --git a/app/tests/api/mitigations/mitigations-pagination.spec.js b/app/tests/api/mitigations/mitigations-pagination.spec.js index fa3fa401..f7eb5b6e 100644 --- a/app/tests/api/mitigations/mitigations-pagination.spec.js +++ b/app/tests/api/mitigations/mitigations-pagination.spec.js @@ -1,4 +1,4 @@ -const mitigationsService = require('../../../services/mitigations-service'); +const mitigationsService = require('../../../services/stix/mitigations-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/mitigations/mitigations.spec.js b/app/tests/api/mitigations/mitigations.spec.js index ca5894c5..93100650 100644 --- a/app/tests/api/mitigations/mitigations.spec.js +++ b/app/tests/api/mitigations/mitigations.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -196,10 +196,7 @@ describe('Mitigations API', function () { let mitigation2; it('POST /api/mitigations should create a new version of a mitigation with a duplicate stix.id but different stix.modified date', async function () { - mitigation2 = _.cloneDeep(mitigation1); - mitigation2._id = undefined; - mitigation2.__t = undefined; - mitigation2.__v = undefined; + mitigation2 = cloneForCreate(mitigation1); const timestamp = new Date().toISOString(); mitigation2.stix.modified = timestamp; const body = mitigation2; @@ -218,10 +215,7 @@ describe('Mitigations API', function () { let mitigation3; it('POST /api/mitigations should create a new version of a mitigation with a duplicate stix.id but different stix.modified date', async function () { - mitigation3 = _.cloneDeep(mitigation1); - mitigation3._id = undefined; - mitigation3.__t = undefined; - mitigation3.__v = undefined; + mitigation3 = cloneForCreate(mitigation1); const timestamp = new Date().toISOString(); mitigation3.stix.modified = timestamp; const body = mitigation3; diff --git a/app/tests/api/notes/notes.spec.js b/app/tests/api/notes/notes.spec.js index b47ed430..4651e63b 100644 --- a/app/tests/api/notes/notes.spec.js +++ b/app/tests/api/notes/notes.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -213,10 +213,7 @@ describe('Notes API', function () { let note2; it('POST /api/notes should create a new version of a note with a duplicate stix.id but different stix.modified date', async function () { - note2 = _.cloneDeep(note1); - note2._id = undefined; - note2.__t = undefined; - note2.__v = undefined; + note2 = cloneForCreate(note1); const timestamp = new Date().toISOString(); note2.stix.abstract = 'This is the abstract for a note.'; note2.stix.content = 'Still a note. Parchment.'; @@ -255,10 +252,7 @@ describe('Notes API', function () { let note3; it('POST /api/notes should create a new note with a new stix.id', async function () { - note3 = _.cloneDeep(note1); - note3._id = undefined; - note3.__t = undefined; - note3.__v = undefined; + note3 = cloneForCreate(note1); note3.stix.id = undefined; const timestamp = new Date().toISOString(); note3.stix.abstract = 'This is the abstract for a note.'; @@ -280,10 +274,7 @@ describe('Notes API', function () { let note4; it('POST /api/notes should create a new version of the last note with a duplicate stix.id but different stix.modified date', async function () { - note4 = _.cloneDeep(note3); - note4._id = undefined; - note4.__t = undefined; - note4.__v = undefined; + note4 = cloneForCreate(note3); const timestamp = new Date().toISOString(); note4.stix.abstract = 'This is the abstract for a note. Parchment'; note4.stix.content = 'Still a note.'; diff --git a/app/tests/api/recent-activity/recent-activity.spec.js b/app/tests/api/recent-activity/recent-activity.spec.js index e7ec1073..fb252b8b 100644 --- a/app/tests/api/recent-activity/recent-activity.spec.js +++ b/app/tests/api/recent-activity/recent-activity.spec.js @@ -11,7 +11,7 @@ const login = require('../../shared/login'); const logger = require('../../../lib/logger'); logger.level = 'debug'; -const collectionBundlesService = require('../../../services/collection-bundles-service'); +const collectionBundlesService = require('../../../services/stix/collection-bundles-service'); async function readJson(path) { const data = await fs.readFile(require.resolve(path)); diff --git a/app/tests/api/relationships/relationships-pagination.spec.js b/app/tests/api/relationships/relationships-pagination.spec.js index a196add8..194ebcc6 100644 --- a/app/tests/api/relationships/relationships-pagination.spec.js +++ b/app/tests/api/relationships/relationships-pagination.spec.js @@ -1,4 +1,4 @@ -const relationshipsService = require('../../../services/relationships-service'); +const relationshipsService = require('../../../services/stix/relationships-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 06f6536e..2e239929 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -195,10 +195,7 @@ describe('Relationships API', function () { let relationship1b; it('POST /api/relationships should create a new version of a relationship with a duplicate stix.id but different stix.modified date', async function () { - relationship1b = _.cloneDeep(relationship1a); - relationship1b._id = undefined; - relationship1b.__t = undefined; - relationship1b.__v = undefined; + relationship1b = cloneForCreate(relationship1a); const timestamp = new Date().toISOString(); relationship1b.stix.modified = timestamp; const body = relationship1b; @@ -217,10 +214,7 @@ describe('Relationships API', function () { let relationship1c; it('POST /api/relationships should create a new version of a relationship with a duplicate stix.id but different stix.modified date', async function () { - relationship1c = _.cloneDeep(relationship1a); - relationship1c._id = undefined; - relationship1c.__t = undefined; - relationship1c.__v = undefined; + relationship1c = cloneForCreate(relationship1a); const timestamp = new Date().toISOString(); relationship1c.stix.modified = timestamp; const body = relationship1c; diff --git a/app/tests/api/software/software-pagination.spec.js b/app/tests/api/software/software-pagination.spec.js index 7a92211e..2d9a038f 100644 --- a/app/tests/api/software/software-pagination.spec.js +++ b/app/tests/api/software/software-pagination.spec.js @@ -1,4 +1,4 @@ -const softwareService = require('../../../services/software-service'); +const softwareService = require('../../../services/stix/software-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/software/software.spec.js b/app/tests/api/software/software.spec.js index 5052b621..4c96d146 100644 --- a/app/tests/api/software/software.spec.js +++ b/app/tests/api/software/software.spec.js @@ -7,6 +7,7 @@ const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -240,10 +241,7 @@ describe('Software API', function () { let software2; it('POST /api/software should create a new version of a software with a duplicate stix.id but different stix.modified date', async function () { - software2 = _.cloneDeep(software1); - software2._id = undefined; - software2.__t = undefined; - software2.__v = undefined; + software2 = cloneForCreate(software1); const timestamp = new Date().toISOString(); software2.stix.modified = timestamp; const body = software2; @@ -327,10 +325,7 @@ describe('Software API', function () { let software3; it('POST /api/software should create a new version of a software with a duplicate stix.id but different stix.modified date', async function () { - software3 = _.cloneDeep(software1); - software3._id = undefined; - software3.__t = undefined; - software3.__v = undefined; + software3 = cloneForCreate(software1); const timestamp = new Date().toISOString(); software3.stix.modified = timestamp; const body = software3; diff --git a/app/tests/api/system-configuration/create-object-identity.spec.js b/app/tests/api/system-configuration/create-object-identity.spec.js index 0bdbc2b3..3819751f 100644 --- a/app/tests/api/system-configuration/create-object-identity.spec.js +++ b/app/tests/api/system-configuration/create-object-identity.spec.js @@ -1,11 +1,11 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const uuid = require('uuid'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -140,10 +140,7 @@ describe('Create Object with Organization Identity API', function () { }); it('POST /api/tactics creates a new version of the tactic', async function () { - const tactic2 = _.cloneDeep(tactic1); - tactic2._id = undefined; - tactic2.__t = undefined; - tactic2.__v = undefined; + const tactic2 = cloneForCreate(tactic1); const timestamp = new Date().toISOString(); tactic2.stix.modified = timestamp; const body = tactic2; diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index 7097e94a..c3b1e1aa 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -189,10 +189,7 @@ describe('Tactics API', function () { let tactic2; it('POST /api/tactics should create a new version of a tactic with a duplicate stix.id but different stix.modified date', async function () { - tactic2 = _.cloneDeep(tactic1); - tactic2._id = undefined; - tactic2.__t = undefined; - tactic2.__v = undefined; + tactic2 = cloneForCreate(tactic1); const timestamp = new Date().toISOString(); tactic2.stix.description = 'Still a tactic. Red.'; tactic2.stix.modified = timestamp; @@ -212,10 +209,7 @@ describe('Tactics API', function () { let tactic3; it('POST /api/tactics should create a new version of a tactic with a duplicate stix.id but different stix.modified date', async function () { - tactic3 = _.cloneDeep(tactic1); - tactic3._id = undefined; - tactic3.__t = undefined; - tactic3.__v = undefined; + tactic3 = cloneForCreate(tactic1); const timestamp = new Date().toISOString(); tactic3.stix.description = 'Still a tactic. Violet.'; tactic3.stix.modified = timestamp; diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index 2fc21bfb..fb104c90 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -11,7 +11,7 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const collectionBundlesService = require('../../../services/collection-bundles-service'); +const collectionBundlesService = require('../../../services/stix/collection-bundles-service'); async function readJson(path) { const data = await fs.readFile(require.resolve(path)); diff --git a/app/tests/api/techniques/techniques-pagination.spec.js b/app/tests/api/techniques/techniques-pagination.spec.js index 404aa76e..2d6f0c8b 100644 --- a/app/tests/api/techniques/techniques-pagination.spec.js +++ b/app/tests/api/techniques/techniques-pagination.spec.js @@ -1,4 +1,4 @@ -const techniquesService = require('../../../services/techniques-service'); +const techniquesService = require('../../../services/stix/techniques-service'); const PaginationTests = require('../../shared/pagination'); // modified and created properties will be set before calling REST API diff --git a/app/tests/api/techniques/techniques.query.spec.js b/app/tests/api/techniques/techniques.query.spec.js index 340af8be..0e7731f2 100644 --- a/app/tests/api/techniques/techniques.query.spec.js +++ b/app/tests/api/techniques/techniques.query.spec.js @@ -13,7 +13,7 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const techniquesService = require('../../../services/techniques-service'); +const techniquesService = require('../../../services/stix/techniques-service'); function asyncWait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/app/tests/api/techniques/techniques.spec.js b/app/tests/api/techniques/techniques.spec.js index 743a19b7..abbf221b 100644 --- a/app/tests/api/techniques/techniques.spec.js +++ b/app/tests/api/techniques/techniques.spec.js @@ -1,12 +1,12 @@ const request = require('supertest'); const { expect } = require('expect'); -const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const config = require('../../../config/config'); const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -229,10 +229,7 @@ describe('Techniques Basic API', function () { let technique2; it('POST /api/techniques should create a new version of a technique with a duplicate stix.id but different stix.modified date', async function () { - technique2 = _.cloneDeep(technique1); - technique2._id = undefined; - technique2.__t = undefined; - technique2.__v = undefined; + technique2 = cloneForCreate(technique1); const timestamp = new Date().toISOString(); technique2.stix.modified = timestamp; technique2.stix.description = 'Still a technique. Purple!'; @@ -252,10 +249,7 @@ describe('Techniques Basic API', function () { let technique3; it('POST /api/techniques should create a new version of a technique with a duplicate stix.id but different stix.modified date', async function () { - technique3 = _.cloneDeep(technique1); - technique3._id = undefined; - technique3.__t = undefined; - technique3.__v = undefined; + technique3 = cloneForCreate(technique1); const timestamp = new Date().toISOString(); technique3.stix.modified = timestamp; technique3.stix.description = 'Still a technique. Blue!'; diff --git a/app/tests/api/techniques/techniques.tactics.spec.js b/app/tests/api/techniques/techniques.tactics.spec.js index 055a0bef..d5d68ca2 100644 --- a/app/tests/api/techniques/techniques.tactics.spec.js +++ b/app/tests/api/techniques/techniques.tactics.spec.js @@ -11,7 +11,7 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const collectionBundlesService = require('../../../services/collection-bundles-service'); +const collectionBundlesService = require('../../../services/stix/collection-bundles-service'); async function readJson(path) { const data = await fs.readFile(require.resolve(path)); diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index f5b4257f..3708a974 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -8,7 +8,7 @@ const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const UserAccount = require('../../../models/user-account-model'); const Team = require('../../../models/team-model'); -const teamsService = require('../../../services/teams-service'); +const teamsService = require('../../../services/system/teams-service'); const login = require('../../shared/login'); diff --git a/app/tests/config/config.spec.js b/app/tests/config/config.spec.js index 9a06b12a..25e2f7c2 100644 --- a/app/tests/config/config.spec.js +++ b/app/tests/config/config.spec.js @@ -4,7 +4,7 @@ process.env.JSON_CONFIG_PATH = './app/tests/config/test-config.json'; const { expect } = require('expect'); const config = require('../../config/config'); -const markingDefinitionsService = require('../../services/marking-definitions-service'); +const markingDefinitionsService = require('../../services/stix/marking-definitions-service'); const database = require('../../lib/database-in-memory'); const databaseConfiguration = require('../../lib/database-configuration'); diff --git a/app/tests/shared/clone-for-create.js b/app/tests/shared/clone-for-create.js new file mode 100644 index 00000000..b16d297c --- /dev/null +++ b/app/tests/shared/clone-for-create.js @@ -0,0 +1,54 @@ +'use strict'; + +const _ = require('lodash'); +const config = require('../../config/config'); + +/** + * Creates a deep clone of an ATT&CK object suitable for POST requests. + * + * This utility strips all backend-controlled and database-specific fields that would + * cause ImmutablePropertyError or other validation errors when creating new versions + * of existing objects via POST requests. + * + * Fields removed: + * - workspace.attack_id: Backend generates ATT&CK IDs; clients cannot set them + * - MITRE ATT&CK external references: Backend controls official ATT&CK citations + * - MongoDB fields: _id, __t, __v (database-specific fields) + * + * @param {Object} attackObject - The ATT&CK object to clone + * @returns {Object} A deep clone suitable for POST requests + * + * @example + * // Clone an existing technique to create a new version + * const technique2 = cloneForCreate(technique1); + * technique2.stix.modified = new Date().toISOString(); + * technique2.stix.description = 'Updated description'; + * // Now safe to POST technique2 + */ +function cloneForCreate(attackObject) { + // Perform deep clone + const cloned = _.cloneDeep(attackObject); + + // Remove MongoDB-specific fields + delete cloned._id; + delete cloned.__t; + delete cloned.__v; + + // Remove backend-controlled ATT&CK ID from workspace + if (cloned.workspace && cloned.workspace.attack_id) { + delete cloned.workspace.attack_id; + } + + // Remove MITRE ATT&CK external references (backend controls these) + if (cloned.stix && cloned.stix.external_references) { + cloned.stix.external_references = cloned.stix.external_references.filter( + (ref) => !config.attackSourceNames.includes(ref.source_name), + ); + } + + return cloned; +} + +module.exports = { + cloneForCreate, +}; From 9915f79096b15eeb5fe9483473b31ef314118921 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:28:16 -0500 Subject: [PATCH 109/370] test: continue updating regression tests --- .../{analytics-spec.js => analytics.spec.js} | 8 +------- app/tests/api/groups/groups.query.spec.js | 16 ---------------- 2 files changed, 1 insertion(+), 23 deletions(-) rename app/tests/api/analytics/{analytics-spec.js => analytics.spec.js} (99%) diff --git a/app/tests/api/analytics/analytics-spec.js b/app/tests/api/analytics/analytics.spec.js similarity index 99% rename from app/tests/api/analytics/analytics-spec.js rename to app/tests/api/analytics/analytics.spec.js index 10ad394b..4ce8a4a0 100644 --- a/app/tests/api/analytics/analytics-spec.js +++ b/app/tests/api/analytics/analytics.spec.js @@ -24,13 +24,7 @@ const initialObjectData = { name: 'analytic-1', spec_version: '2.1', type: 'x-mitre-analytic', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'AN9999', - url: 'https://attack.mitre.org/analytics/AN9999', - }, - ], + // Note: external_references will be generated by backend description: 'Description of an analytic', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index d12d17b5..4a5b5071 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -25,31 +25,20 @@ async function readJson(path) { return JSON.parse(data); } -function makeExternalReference(attackId) { - return { - source_name: 'mitre-attack', - external_id: attackId, - url: `https://attack.mitre.org/groups/${attackId}`, - }; -} - async function configureGroups(baseGroup, userAccountId1, userAccountId2) { const groups = []; // x_mitre_deprecated,revoked undefined (user account 1) const data1a = _.cloneDeep(baseGroup); - data1a.stix.external_references.push(makeExternalReference('G0001')); data1a.userAccountId = userAccountId1; groups.push(data1a); // x_mitre_deprecated,revoked undefined (user account 2) const data1b = _.cloneDeep(baseGroup); - data1b.stix.external_references.push(makeExternalReference('G0010')); data1b.userAccountId = userAccountId2; groups.push(data1b); // x_mitre_deprecated = false, revoked = false const data2 = _.cloneDeep(baseGroup); - data2.stix.external_references.push(makeExternalReference('G0002')); data2.stix.x_mitre_deprecated = false; data2.stix.revoked = false; data2.workspace.workflow = { state: 'work-in-progress' }; @@ -58,7 +47,6 @@ async function configureGroups(baseGroup, userAccountId1, userAccountId2) { // x_mitre_deprecated = true, revoked = false const data3 = _.cloneDeep(baseGroup); - data3.stix.external_references.push(makeExternalReference('G0003')); data3.stix.x_mitre_deprecated = true; data3.stix.revoked = false; data3.workspace.workflow = { state: 'awaiting-review' }; @@ -67,7 +55,6 @@ async function configureGroups(baseGroup, userAccountId1, userAccountId2) { // x_mitre_deprecated = false, revoked = true const data4 = _.cloneDeep(baseGroup); - data4.stix.external_references.push(makeExternalReference('G0004')); data4.stix.x_mitre_deprecated = false; data4.stix.revoked = true; data4.workspace.workflow = { state: 'awaiting-review' }; @@ -77,7 +64,6 @@ async function configureGroups(baseGroup, userAccountId1, userAccountId2) { // multiple versions, last version has x_mitre_deprecated = true, revoked = true const data5a = _.cloneDeep(baseGroup); const id = `intrusion-set--${uuid.v4()}`; - data5a.stix.external_references.push(makeExternalReference('G0005')); data5a.stix.id = id; data5a.stix.name = 'multiple-versions'; data5a.workspace.workflow = { state: 'awaiting-review' }; @@ -89,7 +75,6 @@ async function configureGroups(baseGroup, userAccountId1, userAccountId2) { await asyncWait(10); // wait so the modified timestamp can change const data5b = _.cloneDeep(baseGroup); - data5b.stix.external_references.push(makeExternalReference('G0005')); data5b.stix.id = id; data5b.stix.name = 'multiple-versions'; data5b.workspace.workflow = { state: 'awaiting-review' }; @@ -101,7 +86,6 @@ async function configureGroups(baseGroup, userAccountId1, userAccountId2) { await asyncWait(10); const data5c = _.cloneDeep(baseGroup); - data5c.stix.external_references.push(makeExternalReference('G0005')); data5c.stix.id = id; data5c.stix.name = 'multiple-versions'; data5c.workspace.workflow = { state: 'awaiting-review' }; From f187fb95eeca44781cce95b2b7a7708099949f78 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:06:58 -0500 Subject: [PATCH 110/370] fix(BaseService): retrieveById call retrieveLatestByStixIdLean instead of retrieveLatestByStixId The service was calling retrieveLatestByStixId which returns a Mongoose doc. It should be calling retrieveLatestByStixIdLean instead to get a plain object that can have properties added to it post retrieval. This resolve a regression test bug. --- app/services/meta-classes/base.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 53f08c55..aa6555db 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -211,7 +211,7 @@ class BaseService extends ServiceWithHooks { } return documents; } else if (options.versions === 'latest') { - const document = await this.repository.retrieveLatestByStixId(stixId); + const document = await this.repository.retrieveLatestByStixIdLean(stixId); if (document) { try { From 9a330d5a06aa8f4f0f2cf4dd410c2d1f0de6a49e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:10:45 -0500 Subject: [PATCH 111/370] fix(BaseService): restore POST functionality w.r.t attack_id The base create method now permits users to include workspace.attack_id as long as (1) we're creating a new snapshot as opposed to a new object; (2) server-controlled fields specified in the req body match the prev snapshot. This will restore support for the POST endpoint tangential use-case of creating new object snapshots as opposed to exclusively creating new objects. --- app/api/definitions/components/workspace.yml | 3 + app/services/meta-classes/base.service.js | 127 ++++++++++++------- 2 files changed, 86 insertions(+), 44 deletions(-) diff --git a/app/api/definitions/components/workspace.yml b/app/api/definitions/components/workspace.yml index dc5c55d2..32051cbf 100644 --- a/app/api/definitions/components/workspace.yml +++ b/app/api/definitions/components/workspace.yml @@ -14,6 +14,9 @@ components: type: array items: $ref: '#/components/schemas/collection_reference' + attack_id: + type: string + description: 'ATT&CK ID (e.g., T1234, G0001). When creating a new version of an existing object, this must match the existing attack_id. When creating a new object, this field is generated by the backend and cannot be set.' collection_reference: type: object properties: diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index aa6555db..5a7c7531 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -334,65 +334,104 @@ class BaseService extends ServiceWithHooks { const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; const parentTechniqueId = options?.parentTechniqueId; - // SECTION START: CHECKING ILLEGAL OPS - // Throw (reject request) if user attempts to manually define the ATT&CK ID -- this field is controlled exclusively by the backend - if (attackIdInExternalReferences) { - logger.warn( - 'Immutable property: user attempted to set backend-controlled property, external_references.0.external_id', - ); - throw new ImmutablePropertyError('external_references.0.external_id'); - } else if (attackIdInWorkspace) { - logger.warn( - 'Immutable property: user attempted to set backend-controlled property, workspace.attack_id', - ); - throw new ImmutablePropertyError('workspace.attack_id'); + // Check if we're creating a new version of an existing object (same stix.id) + let existingVersion = null; + if (data.stix?.id) { + // Look for any existing version with the same stix.id + const existingVersions = await this.repository.retrieveAllById(data.stix.id); + if (existingVersions && existingVersions.length > 0) { + existingVersion = existingVersions[0]; // Get any version to extract the attack_id + logger.debug( + `Found existing version(s) with stix.id: ${data.stix.id}, will reuse attack_id: ${existingVersion.workspace?.attack_id}`, + ); + } } - if (data.stix?.external_references) { - // On create, clients MUST NOT provide ATT&CK external references - the backend controls this - // Throw (reject request) if user attempts to manually set the MITRE citation in the external_references array - const mitreAttackRefInExternalReferences = - attackIdGenerator.extractAttackIdFromExternalReferences(data.stix); - if (mitreAttackRefInExternalReferences) { - logger.error( - 'User manually attempted to set the MITRE ATT&CK citation at external_references.0', + // SECTION START: CHECKING ILLEGAL OPS + if (existingVersion) { + // POST for existing object (new version/snapshot): Allow client to provide attack_id if it matches + const existingAttackId = existingVersion.workspace?.attack_id; + + if (attackIdInWorkspace && attackIdInWorkspace !== existingAttackId) { + logger.warn( + `Immutable property: user attempted to change workspace.attack_id from ${existingAttackId} to ${attackIdInWorkspace}`, + ); + throw new ImmutablePropertyError('workspace.attack_id', { + details: `Expected '${existingAttackId}' but received '${attackIdInWorkspace}'`, + }); + } + + if (attackIdInExternalReferences && attackIdInExternalReferences !== existingAttackId) { + logger.warn( + `Immutable property: user attempted to change external_references[0].external_id from ${existingAttackId} to ${attackIdInExternalReferences}`, ); - throw new ImmutablePropertyError('external_references.0', { - input: mitreAttackRefInExternalReferences, + throw new ImmutablePropertyError('external_references[0].external_id', { + details: `Expected '${existingAttackId}' but received '${attackIdInExternalReferences}'`, }); } + + // Client provided matching values or no values - both are fine + // We'll use the existing attack_id + } else { + // POST for new object: Reject any client-provided attack_id + if (attackIdInExternalReferences) { + logger.warn( + 'Immutable property: user attempted to set backend-controlled property, external_references.0.external_id', + ); + throw new ImmutablePropertyError('external_references.0.external_id'); + } else if (attackIdInWorkspace) { + logger.warn( + 'Immutable property: user attempted to set backend-controlled property, workspace.attack_id', + ); + throw new ImmutablePropertyError('workspace.attack_id'); + } + } + + if (data.stix?.external_references) { + // Filter out any MITRE ATT&CK external references (we'll add the correct one below) + data.stix.external_references = data.stix.external_references.filter( + (ref) => !config.attackSourceNames.includes(ref.source_name), + ); } else { data.stix.external_references = []; } // SECTION END: CHECKING ILLEGAL OPS - // Generate and set the ATT&CK ID + // Generate or reuse the ATT&CK ID if (attackIdGenerator.requiresAttackId(this.type)) { - // Validate subtechnique requirements - if (isSubtechnique && !parentTechniqueId) { - const errorMessage = - 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).'; - logger.error(errorMessage); - throw new InvalidPostOperationError(errorMessage); - } - if (!isSubtechnique && parentTechniqueId) { - const errorMessage = - 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).'; - logger.error(errorMessage); - throw new InvalidPostOperationError(errorMessage); + let attackId; + + if (existingVersion) { + // Reuse the attack_id from the existing version + attackId = existingVersion.workspace.attack_id; + logger.debug(`Reusing ATT&CK ID from existing version: ${attackId}`); + } else { + // Validate subtechnique requirements + if (isSubtechnique && !parentTechniqueId) { + const errorMessage = + 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).'; + logger.error(errorMessage); + throw new InvalidPostOperationError(errorMessage); + } + if (!isSubtechnique && parentTechniqueId) { + const errorMessage = + 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).'; + logger.error(errorMessage); + throw new InvalidPostOperationError(errorMessage); + } + + // Generate a new ATT&CK ID + attackId = await attackIdGenerator.generateAttackId( + this.type, + this.repository, + isSubtechnique, + parentTechniqueId, + ); + logger.debug(`Generated new ATT&CK ID: ${attackId}`); } - // Set the ATT&CK ID! - const attackId = await attackIdGenerator.generateAttackId( - this.type, - this.repository, - isSubtechnique, - parentTechniqueId, - ); - data.workspace = data.workspace || {}; data.workspace.attack_id = attackId; - logger.debug(`Set the ATT&CK ID: ${attackId}`); } // Generate and add the ATT&CK external reference From 37a75051f7db9cd9f189a9c7f10cdc14d687d831 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:12:05 -0500 Subject: [PATCH 112/370] refactor(detection-strategies-controller): use global error handler for service layer exceptions --- .../detection-strategies-controller.js | 62 +++++-------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/app/controllers/detection-strategies-controller.js b/app/controllers/detection-strategies-controller.js index ca970c3b..88cd6d48 100644 --- a/app/controllers/detection-strategies-controller.js +++ b/app/controllers/detection-strategies-controller.js @@ -2,13 +2,8 @@ const detectionStrategiesService = require('../services/stix/detection-strategies-service'); const logger = require('../lib/logger'); -const { - DuplicateIdError, - BadlyFormattedParameterError, - InvalidQueryStringParameterError, -} = require('../exceptions'); -exports.retrieveAll = async function (req, res) { +exports.retrieveAll = async function (req, res, next) { const options = { offset: req.query.offset || 0, limit: req.query.limit || 0, @@ -32,12 +27,11 @@ exports.retrieveAll = async function (req, res) { } return res.status(200).send(results); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get detection strategies. Server error.'); + next(err); } }; -exports.retrieveById = async function (req, res) { +exports.retrieveById = async function (req, res, next) { const options = { versions: req.query.versions || 'latest', }; @@ -56,20 +50,11 @@ exports.retrieveById = async function (req, res) { return res.status(200).send(detectionStrategies); } } catch (err) { - if (err instanceof BadlyFormattedParameterError) { - logger.warn('Badly formatted stix id: ' + req.params.stixId); - return res.status(400).send('Stix id is badly formatted.'); - } else if (err instanceof InvalidQueryStringParameterError) { - logger.warn('Invalid query string: versions=' + req.query.versions); - return res.status(400).send('Query string parameter versions is invalid.'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get detection strategies. Server error.'); - } + next(err); } }; -exports.retrieveVersionById = async function (req, res) { +exports.retrieveVersionById = async function (req, res, next) { try { const detectionStrategy = await detectionStrategiesService.retrieveVersionById( req.params.stixId, @@ -82,17 +67,11 @@ exports.retrieveVersionById = async function (req, res) { return res.status(200).send(detectionStrategy); } } catch (err) { - if (err instanceof BadlyFormattedParameterError) { - logger.warn('Badly formatted stix id: ' + req.params.stixId); - return res.status(400).send('Stix id is badly formatted.'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get detection strategy. Server error.'); - } + next(err); } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { // Get the data from the request const detectionStrategyData = req.body; const options = { @@ -109,21 +88,11 @@ exports.create = async function (req, res) { logger.debug('Success: Created detection strategy with id ' + detectionStrategy.stix.id); return res.status(201).send(detectionStrategy); } catch (err) { - if (err instanceof DuplicateIdError) { - logger.warn('Duplicate stix.id and stix.modified'); - return res - .status(409) - .send( - 'Unable to create detection strategy. Duplicate stix.id and stix.modified properties.', - ); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create detection strategy. Server error.'); - } + next(err); } }; -exports.updateFull = async function (req, res) { +exports.updateFull = async function (req, res, next) { // Get the data from the request const detectionStrategyData = req.body; @@ -141,12 +110,11 @@ exports.updateFull = async function (req, res) { return res.status(200).send(detectionStrategy); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update detection strategy. Server error.'); + next(err); } }; -exports.deleteVersionById = async function (req, res) { +exports.deleteVersionById = async function (req, res, next) { try { const detectionStrategy = await detectionStrategiesService.deleteVersionById( req.params.stixId, @@ -159,12 +127,11 @@ exports.deleteVersionById = async function (req, res) { return res.status(204).end(); } } catch (err) { - logger.error('Delete detection strategy failed. ' + err); - return res.status(500).send('Unable to delete detection strategy. Server error.'); + next(err); } }; -exports.deleteById = async function (req, res) { +exports.deleteById = async function (req, res, next) { try { const detectionStrategies = await detectionStrategiesService.deleteById(req.params.stixId); if (detectionStrategies.deletedCount === 0) { @@ -174,7 +141,6 @@ exports.deleteById = async function (req, res) { return res.status(204).end(); } } catch (err) { - logger.error('Delete detection strategy failed. ' + err); - return res.status(500).send('Unable to delete detection strategy. Server error.'); + next(err); } }; From b4191762503f4c457baf19b13f07203fd3df610a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:12:59 -0500 Subject: [PATCH 113/370] feat(error-handler): enable support for passing custom properties in res.body --- app/lib/error-handler.js | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index 077e8efe..09ee5478 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -51,6 +51,30 @@ exports.requestValidation = function (err, req, res, next) { } }; +/** + * Helper function to build error response with all custom properties + * @param {Error} err - The error object + * @returns {Object|String} Error response object or message string + */ +function buildErrorResponse(err) { + // Start with the message + const errorResponse = { message: err.message }; + + // Add any custom properties from the error (excluding standard Error properties) + const standardErrorProps = ['name', 'message', 'stack']; + let hasCustomProps = false; + + Object.keys(err).forEach((key) => { + if (!standardErrorProps.includes(key)) { + errorResponse[key] = err[key]; + hasCustomProps = true; + } + }); + + // Return object if custom properties exist, otherwise just the message string + return hasCustomProps ? errorResponse : err.message; +} + exports.serviceExceptions = function (err, req, res, next) { // Handle 400 Bad Request errors (user-related errors) if ( @@ -65,7 +89,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof BadRequestError ) { logger.warn(`Bad request: ${err.message}`); - return res.status(400).send(err.message); + return res.status(400).send(buildErrorResponse(err)); } // Handle 404 Not Found errors @@ -77,7 +101,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof DefaultMarkingDefinitionsNotFoundError ) { logger.warn(`Not found: ${err.message}`); - return res.status(404).send(err.message); + return res.status(404).send(buildErrorResponse(err)); } // Handle 409 Conflict errors (duplicate resources) @@ -87,7 +111,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof DuplicateNameError ) { logger.warn(`Conflict: ${err.message}`); - return res.status(409).send(err.message); + return res.status(409).send(buildErrorResponse(err)); } // Handle 500 Internal Server errors (service and system errors) @@ -99,13 +123,13 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof DatabaseError ) { logger.error(`Service error: ${err.message}`); - return res.status(500).send(err.message); + return res.status(500).send(buildErrorResponse(err)); } // Handle 502 Bad Gateway errors (external service errors) if (err instanceof HostNotFoundError || err instanceof ConnectionRefusedError) { logger.error(`Bad gateway: ${err.message}`); - return res.status(502).send(err.message); + return res.status(502).send(buildErrorResponse(err)); } // Handle 503 Service Unavailable errors (HTTP and configuration errors) @@ -115,13 +139,13 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof AnonymousUserAccountNotSetError ) { logger.error(`Service unavailable: ${err.message}`); - return res.status(503).send(err.message); + return res.status(503).send(buildErrorResponse(err)); } // Handle 501 Not Implemented errors if (err instanceof NotImplementedError) { logger.warn(`Not implemented: ${err.message}`); - return res.status(501).send(err.message); + return res.status(501).send(buildErrorResponse(err)); } // Pass through to next error handler if not a recognized exception From 08ecdda36987002e7f5dfd15b630811df08a2428 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:14:25 -0500 Subject: [PATCH 114/370] fix(detection-strategies-service): throw if posting with ref to nonexistent analytic --- .../stix/detection-strategies-service.js | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index b7ed36d7..bf1e299e 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -6,6 +6,7 @@ const { BaseService } = require('../meta-classes'); const { DetectionStrategy: DetectionStrategyType } = require('../../lib/types'); const logger = require('../../lib/logger'); const EventBus = require('../../lib/event-bus'); +const { NotFoundError } = require('../../exceptions'); /** * Service for managing detection strategies @@ -39,27 +40,22 @@ class DetectionStrategiesService extends BaseService { // We emit events in afterCreate/afterUpdate for cross-service WRITES const analyticRefs = data.stix?.x_mitre_analytic_refs || []; for (const analyticId of analyticRefs) { - try { - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - data.workspace.embedded_relationships.push({ - stix_id: analyticId, - attack_id: analytic?.workspace?.attack_id || null, - name: analytic?.stix?.name || null, - direction: 'outbound', - }); - } catch (error) { - logger.warn( - `DetectionStrategiesService: Could not fetch analytic ${analyticId} for outbound relationship`, - error, - ); - // Add relationship without attack_id - data.workspace.embedded_relationships.push({ - stix_id: analyticId, - attack_id: null, - name: null, - direction: 'outbound', + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + + if (!analytic) { + logger.warn(`DetectionStrategiesService: Analytic ${analyticId} does not exist`); + throw new NotFoundError({ + analyticId: analyticId, + message: 'The detection strategy cannot reference an analytic that does not exist', }); } + + data.workspace.embedded_relationships.push({ + stix_id: analyticId, + attack_id: analytic?.workspace?.attack_id || null, + name: analytic?.stix?.name || null, + direction: 'outbound', + }); } } From b69282da2e26dc18240032c5241beabdb1be2f9e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:18:38 -0500 Subject: [PATCH 115/370] refactor(user-accounts-controller): use global error handler for service layer exceptions --- app/controllers/user-accounts-controller.js | 75 +++++---------------- 1 file changed, 18 insertions(+), 57 deletions(-) diff --git a/app/controllers/user-accounts-controller.js b/app/controllers/user-accounts-controller.js index c162bc7a..9ef3a2cd 100644 --- a/app/controllers/user-accounts-controller.js +++ b/app/controllers/user-accounts-controller.js @@ -3,9 +3,8 @@ const userAccountsService = require('../services/system/user-accounts-service'); const logger = require('../lib/logger'); const config = require('../config/config'); -const { BadlyFormattedParameterError, DuplicateEmailError } = require('../exceptions'); -exports.retrieveAll = async function (req, res) { +exports.retrieveAll = async function (req, res, next) { const options = { offset: req.query.offset || 0, limit: req.query.limit || 0, @@ -28,14 +27,11 @@ exports.retrieveAll = async function (req, res) { } return res.status(200).send(results); } catch (err) { - console.log('retrieveall'); - console.log(err); - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get user accounts. Server error.'); + next(err); } }; -exports.retrieveById = async function (req, res) { +exports.retrieveById = async function (req, res, next) { const options = { includeStixIdentity: req.query.includeStixIdentity, }; @@ -49,17 +45,11 @@ exports.retrieveById = async function (req, res) { return res.status(200).send(userAccount); } } catch (err) { - if (err instanceof BadlyFormattedParameterError) { - logger.warn('Badly formatted user account id: ' + req.params.id); - return res.status(400).send('User account id is badly formatted.'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get user account. Server error.'); - } + next(err); } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { // Get the data from the request const userAccountData = req.body; @@ -75,19 +65,11 @@ exports.create = async function (req, res) { logger.debug(`Success: Created user account with id ${userAccount.id}`); return res.status(201).send(userAccount); } catch (err) { - if (err instanceof DuplicateEmailError) { - logger.warn(`Unable to create user account, duplicate email: ${userAccountData.email}`); - return res.status(400).send('Duplicate email'); - } else { - console.log('create'); - console.log(err); - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create user account. Server error.'); - } + next(err); } }; -exports.updateFullAsync = async function (req, res) { +exports.updateFullAsync = async function (req, res, next) { try { // Create the technique const userAccount = await userAccountsService.updateFull(req.params.id, req.body); @@ -98,14 +80,11 @@ exports.updateFullAsync = async function (req, res) { return res.status(200).send(userAccount); } } catch (err) { - console.log('updatefullasync'); - console.log(err); - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update user account. Server error.'); + next(err); } }; -exports.updateFull = async function (req, res) { +exports.updateFull = async function (req, res, next) { // Create the technique try { const userAccount = await userAccountsService.updateFull(req.params.id, req.body); @@ -116,12 +95,11 @@ exports.updateFull = async function (req, res) { return res.status(200).send(userAccount); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update user account. Server error.'); + next(err); } }; -exports.deleteAsync = async function (req, res) { +exports.deleteAsync = async function (req, res, next) { try { const userAccount = await userAccountsService.delete(req.params.id); if (!userAccount) { @@ -131,14 +109,11 @@ exports.deleteAsync = async function (req, res) { return res.status(204).end(); } } catch (err) { - console.log('deleteasync'); - console.log(err); - logger.error('Delete user account failed. ' + err); - return res.status(500).send('Unable to delete user account. Server error.'); + next(err); } }; -exports.delete = async function (req, res) { +exports.delete = async function (req, res, next) { try { const userAccount = await userAccountsService.delete(req.params.id); if (!userAccount) { @@ -148,14 +123,11 @@ exports.delete = async function (req, res) { return res.status(204).end(); } } catch (err) { - console.log('delete'); - console.log(err); - logger.error('Delete user account failed. ' + err); - return res.status(500).send('Unable to delete user account. Server error.'); + next(err); } }; -exports.register = async function (req, res) { +exports.register = async function (req, res, next) { // The function supports self-registration of a logged in user if (config.userAuthn.mechanism === 'anonymous') { @@ -188,19 +160,11 @@ exports.register = async function (req, res) { logger.debug(`Success: Registed user account with id ${userAccount.id}`); return res.status(201).send(userAccount); } catch (err) { - console.log('register'); - console.log(err); - if (err.message === userAccountsService.errors.duplicateEmail) { - logger.warn(`Unable to register user account, duplicate email: ${userAccountData.email}`); - return res.status(400).send('Duplicate email'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to register user account. Server error.'); - } + next(err); } }; -exports.retrieveTeamsByUserId = async function (req, res) { +exports.retrieveTeamsByUserId = async function (req, res, next) { const options = { offset: req.query.offset || 0, limit: req.query.limit || 0, @@ -220,9 +184,6 @@ exports.retrieveTeamsByUserId = async function (req, res) { } return res.status(200).send(results); } catch (err) { - console.log('retrieveTeamsByUserId'); - console.log(err); - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get teams. Server error.'); + next(err); } }; From df42c2947601d31dfef7b56d894c641ec3319a81 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:19:34 -0500 Subject: [PATCH 116/370] refactor(data-sources-service): remove console debug statements --- app/services/stix/data-sources-service.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/services/stix/data-sources-service.js b/app/services/stix/data-sources-service.js index 7dc8810f..35d1380e 100644 --- a/app/services/stix/data-sources-service.js +++ b/app/services/stix/data-sources-service.js @@ -44,10 +44,6 @@ class DataSourcesService extends BaseService { includeRevoked: true, }); - console.log('DEBUG: allDataComponents type:', typeof allDataComponents); - console.log('DEBUG: allDataComponents is array:', Array.isArray(allDataComponents)); - console.log('DEBUG: allDataComponents:', JSON.stringify(allDataComponents, null, 2)); - // Add the data components that reference the data source dataSource.dataComponents = allDataComponents.filter( (dataComponent) => dataComponent.stix.x_mitre_data_source_ref === dataSource.stix.id, From 1253389d7bc1b50b19310b50ab0f13d200b9a028 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:21:29 -0500 Subject: [PATCH 117/370] test: continue updating regression tests --- app/tests/api/collections/collections.spec.js | 30 +---- .../data-components/data-components.spec.js | 2 +- .../api/data-sources/data-sources.spec.js | 2 +- .../detection-strategies-pagination.spec.js | 17 +-- .../detection-strategies-spec.js | 116 +++++++++++++++--- app/tests/api/tactics/tactics.spec.js | 6 +- .../techniques/techniques-pagination.spec.js | 2 +- app/tests/api/techniques/techniques.spec.js | 30 +++-- .../api/user-accounts/user-accounts.spec.js | 2 +- 9 files changed, 134 insertions(+), 73 deletions(-) diff --git a/app/tests/api/collections/collections.spec.js b/app/tests/api/collections/collections.spec.js index 48602306..65eea409 100644 --- a/app/tests/api/collections/collections.spec.js +++ b/app/tests/api/collections/collections.spec.js @@ -35,7 +35,8 @@ const initialCollectionData = { spec_version: '2.1', type: 'x-mitre-collection', description: 'This is a collection.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + // Note: external_references will be generated by backend + // external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_contents: [], @@ -53,14 +54,7 @@ const mitigationData1 = { spec_version: '2.1', type: 'course-of-action', description: 'This is a mitigation.', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'T9999', - url: 'https://attack.mitre.org/mitigations/T9999', - }, - { source_name: 'source-1', external_id: 's1' }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', @@ -78,14 +72,7 @@ const mitigationData2 = { spec_version: '2.1', type: 'course-of-action', description: 'This is another mitigation.', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'T9999', - url: 'https://attack.mitre.org/mitigations/T9999', - }, - { source_name: 'source-1', external_id: 's1' }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', @@ -104,14 +91,7 @@ const softwareData = { type: 'malware', description: 'This is a malware type of software.', is_family: false, - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'S3333', - url: 'https://attack.mitre.org/software/S3333', - }, - { source_name: 'source-1', external_id: 's1' }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index c006f3fe..2b87faad 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -250,7 +250,7 @@ describe('Data Components API', function () { }); it('POST /api/data-components does not create a data component with the same id and modified date', async function () { - const body = dataComponent1; + const body = cloneForCreate(dataComponent1); await request(app) .post('/api/data-components') .send(body) diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index 107d21ed..e8fd61d7 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -244,7 +244,7 @@ describe('Data Sources API', function () { const timestamp = new Date().toISOString(); dataSource1.stix.modified = timestamp; dataSource1.stix.description = 'This is an updated data source.'; - const body = dataSource1; + const body = cloneForCreate(dataSource1); const res = await request(app) .put('/api/data-sources/' + dataSource1.stix.id + '/modified/' + originalModified) .send(body) diff --git a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js index c91a7c9b..cb3de3f7 100644 --- a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js @@ -13,22 +13,17 @@ const initialObjectData = { name: 'detection-strategy-1', spec_version: '2.1', type: 'x-mitre-detection-strategy', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'DET9999', - url: 'https://attack.mitre.org/detection-strategies/DET9999', - }, - ], + // Note: external_references will be generated by backend object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', x_mitre_attack_spec_version: '4.0.0', x_mitre_domains: ['enterprise-attack'], - x_mitre_analytic_refs: [ - 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', - 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', - ], + /** + * NOTE: Do not set embedded relationships (x_mitre_analytic_refs) unless you can + * guarantee they exist before the detection strategy is posted. The backend will + * throw in you reference a nonexistent analytic + */ }, }; diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index d73e552c..2b3a272b 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -13,7 +13,7 @@ logger.level = 'debug'; // modified and created properties will be set before calling REST API // stix.id property will be created by REST API -const initialObjectData = { +const initialPostContentForDetectionStrategy = { workspace: { workflow: { state: 'work-in-progress', @@ -23,21 +23,79 @@ const initialObjectData = { name: 'detection-strategy-1', spec_version: '2.1', type: 'x-mitre-detection-strategy', - external_references: [ + // Note: external_references will be generated by backend + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + x_mitre_domains: ['enterprise-attack'], + x_mitre_analytic_refs: [ + 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match! + 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', // must match! + ], + }, +}; + +const initialPostContentForAnalytic1 = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + id: 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match the analytic's embedded ref! + name: 'analytic-1', + spec_version: '2.1', + type: 'x-mitre-analytic', + // Note: external_references will be generated by backend + description: 'Description of an analytic', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + x_mitre_platforms: ['windows'], + x_mitre_domains: ['enterprise-attack'], + x_mitre_mutable_elements: [ { - source_name: 'mitre-attack', - external_id: 'DET9999', - url: 'https://attack.mitre.org/detection-strategies/DET9999', + field: 'fieldOne', + description: 'Description of fieldOne', + }, + { + field: 'fieldTwo', + description: 'Description of fieldTwo', }, ], + }, +}; + +const initialPostContentForAnalytic2 = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + id: 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', // must match the analytic's embedded ref! + name: 'analytic-2', + spec_version: '2.1', + type: 'x-mitre-analytic', + // Note: external_references will be generated by backend + description: 'Description of an analytic', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', x_mitre_attack_spec_version: '3.3.0', + x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], - x_mitre_analytic_refs: [ - 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', - 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', + x_mitre_mutable_elements: [ + { + field: 'fieldOne', + description: 'Description of fieldOne', + }, + { + field: 'fieldTwo', + description: 'Description of fieldTwo', + }, ], }, }; @@ -89,19 +147,47 @@ describe('Detection Strategies API', function () { let detectionStrategy1; it('POST /api/detection-strategies creates a detection strategy', async function () { const timestamp = new Date().toISOString(); - initialObjectData.stix.created = timestamp; - initialObjectData.stix.modified = timestamp; - const body = initialObjectData; - const res = await request(app) + + initialPostContentForDetectionStrategy.stix.created = timestamp; + initialPostContentForDetectionStrategy.stix.modified = timestamp; + + initialPostContentForAnalytic1.stix.created = timestamp; + initialPostContentForAnalytic1.stix.modified = timestamp; + + initialPostContentForAnalytic2.stix.created = timestamp; + initialPostContentForAnalytic2.stix.modified = timestamp; + + // First, post the 2 analytics + // The detection strategy cannot reference analytics that do not exist! + + await request(app) + .post('/api/analytics') + .send(initialPostContentForAnalytic1) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + await request(app) + .post('/api/analytics') + .send(initialPostContentForAnalytic2) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + // Now we can post the detection strategy! + + const resDetectionStrategy = await request(app) .post('/api/detection-strategies') - .send(body) + .send(initialPostContentForDetectionStrategy) .set('Accept', 'application/json') .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); // We expect to get the created detection strategy - detectionStrategy1 = res.body; + detectionStrategy1 = resDetectionStrategy.body; expect(detectionStrategy1).toBeDefined(); expect(detectionStrategy1.stix).toBeDefined(); expect(detectionStrategy1.stix.id).toBeDefined(); @@ -198,7 +284,7 @@ describe('Detection Strategies API', function () { }); it('POST /api/detection-strategies does not create a detection strategy with the same id and modified date', async function () { - const body = detectionStrategy1; + const body = cloneForCreate(detectionStrategy1); await request(app) .post('/api/detection-strategies') .send(body) diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index c3b1e1aa..76ed7a1d 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -177,8 +177,10 @@ describe('Tactics API', function () { }); it('POST /api/tactics does not create a tactic with the same id and modified date', async function () { - const body = tactic1; - // We expect to get the created tactic + // Clone the tactic to remove backend-controlled fields, but keep the same modified date + const body = cloneForCreate(tactic1); + body.stix.modified = tactic1.stix.modified; // Keep the same modified date to trigger duplicate check + // We expect to get a 409 Conflict error await request(app) .post('/api/tactics') .send(body) diff --git a/app/tests/api/techniques/techniques-pagination.spec.js b/app/tests/api/techniques/techniques-pagination.spec.js index 2d6f0c8b..4ca8934d 100644 --- a/app/tests/api/techniques/techniques-pagination.spec.js +++ b/app/tests/api/techniques/techniques-pagination.spec.js @@ -13,7 +13,7 @@ const initialObjectData = { spec_version: '2.1', type: 'attack-pattern', description: 'This is a technique.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + // Removed external_references - backend generates ATT&CK IDs and external references object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], diff --git a/app/tests/api/techniques/techniques.spec.js b/app/tests/api/techniques/techniques.spec.js index abbf221b..5ddf66f8 100644 --- a/app/tests/api/techniques/techniques.spec.js +++ b/app/tests/api/techniques/techniques.spec.js @@ -24,19 +24,12 @@ const initialObjectData = { spec_version: '2.1', type: 'attack-pattern', description: 'This is a technique. Orange.', - external_references: [ - { - source_name: 'mitre-attack', - external_id: 'T9999', - url: 'https://attack.mitre.org/techniques/T9999', - }, - { source_name: 'source-1', external_id: 's1' }, - ], + // external_references: [ object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], x_mitre_modified_by_ref: 'identity--d6424da5-85a0-496e-ae17-494499271108', - x_mitre_data_sources: ['data-source-1', 'data-source-2'], + // x_mitre_data_sources: ['data-source-1', 'data-source-2'], // TODO field is deprecated x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, x_mitre_impact_type: ['impact-1'], @@ -165,9 +158,12 @@ describe('Techniques Basic API', function () { ); expect(technique.stix.created_by_ref).toBe(technique1.stix.created_by_ref); expect(technique.stix.x_mitre_modified_by_ref).toBe(technique1.stix.x_mitre_modified_by_ref); - expect(technique.stix.x_mitre_data_sources).toEqual( - expect.arrayContaining(technique1.stix.x_mitre_data_sources), - ); + // x_mitre_data_sources is deprecated and not set in the test data + if (technique1.stix.x_mitre_data_sources) { + expect(technique.stix.x_mitre_data_sources).toEqual( + expect.arrayContaining(technique1.stix.x_mitre_data_sources), + ); + } expect(technique.stix.x_mitre_detection).toBe(technique1.stix.x_mitre_detection); expect(technique.stix.x_mitre_is_subtechnique).toBe(technique1.stix.x_mitre_is_subtechnique); expect(technique.stix.x_mitre_impact_type).toEqual( @@ -201,7 +197,7 @@ describe('Techniques Basic API', function () { const timestamp = new Date().toISOString(); technique1.stix.modified = timestamp; technique1.stix.description = 'This is an updated technique.'; - const body = technique1; + const body = cloneForCreate(technique1); const res = await request(app) .put('/api/techniques/' + technique1.stix.id + '/modified/' + originalModified) .send(body) @@ -218,7 +214,7 @@ describe('Techniques Basic API', function () { }); it('POST /api/techniques does not create a technique with the same id and modified date', async function () { - const body = technique1; + const body = cloneForCreate(technique1); await request(app) .post('/api/techniques') .send(body) @@ -355,8 +351,10 @@ describe('Techniques Basic API', function () { }); it('GET /api/techniques uses the search parameter (ATT&CK ID) to return the latest version of the technique', async function () { + // Use the auto-generated ATT&CK ID from technique1 + const attackId = technique1.workspace.attack_id; const res = await request(app) - .get('/api/techniques?search=T9999') + .get(`/api/techniques?search=${attackId}`) .set('Accept', 'application/json') .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) .expect(200) @@ -374,7 +372,7 @@ describe('Techniques Basic API', function () { expect(technique.stix).toBeDefined(); expect(technique.stix.id).toBe(technique3.stix.id); expect(technique.stix.modified).toBe(technique3.stix.modified); - expect(technique.workspace.attack_id).toEqual('T9999'); + expect(technique.workspace.attack_id).toEqual(attackId); }); it('GET /api/techniques should not get the first version of the techniques when using the search parameter', async function () { diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index 3708a974..a0fcd781 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -211,7 +211,7 @@ describe('User Accounts API', function () { .send(body) .set('Accept', 'application/json') .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(400); + .expect(409); }); it('DELETE /api/user-accounts deletes a user account', async function () { From c5c3a42e51a072e3c5bf4aba1542efc46d264d52 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:32:58 -0500 Subject: [PATCH 118/370] fix(marking-definitions-service): correct updateFull method signature for non-versioned objects Marking definitions don't support the 'modified' property and versioning like other STIX objects. The updateFull method was incorrectly calling super.updateFull(stixId, data) which expected three parameters (stixId, stixModified, data), causing it to treat 'data' as 'stixModified' and leaving the actual data parameter undefined. This resulted in "Cannot read properties of undefined (reading 'workspace')" errors when attempting to update marking definitions. Fixed by implementing a custom updateFull that retrieves the document by stixId only (without version) and updates it directly, matching the non-versioned nature of marking definitions. Fixes regression test: PUT /api/marking-definitions updates a marking definition --- app/services/stix/marking-definitions-service.js | 9 ++++++++- .../api/marking-definitions/marking-definitions.spec.js | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/services/stix/marking-definitions-service.js b/app/services/stix/marking-definitions-service.js index f7a56155..b6205d56 100644 --- a/app/services/stix/marking-definitions-service.js +++ b/app/services/stix/marking-definitions-service.js @@ -71,7 +71,14 @@ class MarkingDefinitionsService extends BaseService { throw new CannotUpdateStaticObjectError(); } - const newDoc = await super.updateFull(stixId, data); + // Marking definitions don't support versioning (no modified property) + // So we retrieve by stixId only and update directly + const document = await this.repository.retrieveOneById(stixId); + if (!document) { + return null; + } + + const newDoc = await this.repository.updateAndSave(document, data); return newDoc; } diff --git a/app/tests/api/marking-definitions/marking-definitions.spec.js b/app/tests/api/marking-definitions/marking-definitions.spec.js index d0b5707a..9dac7964 100644 --- a/app/tests/api/marking-definitions/marking-definitions.spec.js +++ b/app/tests/api/marking-definitions/marking-definitions.spec.js @@ -1,6 +1,7 @@ const request = require('supertest'); const { expect } = require('expect'); +const { cloneForCreate } = require('../../shared/clone-for-create'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); @@ -148,7 +149,7 @@ describe('Marking Definitions API', function () { it('PUT /api/marking-definitions updates a marking definition', async function () { markingDefinition1.stix.description = 'This is an updated marking definition.'; - const body = markingDefinition1; + const body = cloneForCreate(markingDefinition1); const res = await request(app) .put('/api/marking-definitions/' + markingDefinition1.stix.id) .send(body) @@ -164,7 +165,7 @@ describe('Marking Definitions API', function () { }); it('POST /api/marking-definitions does not create a marking definition with the same id', async function () { - const body = markingDefinition1; + const body = cloneForCreate(markingDefinition1); await request(app) .post('/api/marking-definitions') .send(body) From e12293a4776e806cb0b3a9fe3d83dc1d016904ce Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:43:45 -0500 Subject: [PATCH 119/370] fix(data-sources-service): make auxiliary methods non static There is no functional purpose to track these as static methods. --- app/services/stix/data-sources-service.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/services/stix/data-sources-service.js b/app/services/stix/data-sources-service.js index 35d1380e..477cfce9 100644 --- a/app/services/stix/data-sources-service.js +++ b/app/services/stix/data-sources-service.js @@ -1,7 +1,6 @@ 'use strict'; const dataSourcesRepository = require('../../repository/data-sources-repository'); -const identitiesService = require('./identities-service'); const dataComponentsService = require('./data-components-service'); const { BaseService } = require('../meta-classes'); const { DataSource: DataSourceType } = require('../../lib/types'); @@ -20,20 +19,20 @@ class DataSourcesService extends BaseService { invalidQueryStringParameter: 'Invalid query string parameter', }; - static async addExtraData(dataSource, retrieveDataComponents) { - await identitiesService.addCreatedByAndModifiedByIdentities(dataSource); + async addExtraData(dataSource, retrieveDataComponents) { + await this.addCreatedByAndModifiedByIdentities(dataSource); if (retrieveDataComponents) { - await DataSourcesService.addDataComponents(dataSource); + await this.addDataComponents(dataSource); } } - static async addExtraDataToAll(dataSources, retrieveDataComponents) { + async addExtraDataToAll(dataSources, retrieveDataComponents) { for (const dataSource of dataSources) { - await DataSourcesService.addExtraData(dataSource, retrieveDataComponents); + await this.addExtraData(dataSource, retrieveDataComponents); } } - static async addDataComponents(dataSource) { + async addDataComponents(dataSource) { // We have to work with the latest version of all data components to avoid mishandling a situation // where an earlier version of a data component may reference a data source, but the latest // version doesn't. @@ -61,14 +60,14 @@ class DataSourcesService extends BaseService { if (options.versions === 'all') { const dataSources = await this.repository.retrieveAllById(stixId); - await DataSourcesService.addExtraDataToAll(dataSources, options.retrieveDataComponents); + await this.addExtraDataToAll(dataSources, options.retrieveDataComponents); return dataSources; } else if (options.versions === 'latest') { const dataSource = await this.repository.retrieveLatestByStixId(stixId); // Note: document is null if not found if (dataSource) { - await DataSourcesService.addExtraData(dataSource, options.retrieveDataComponents); + await this.addExtraData(dataSource, options.retrieveDataComponents); return [dataSource]; } else { return []; @@ -101,7 +100,7 @@ class DataSourcesService extends BaseService { // Note: document is null if not found if (dataSource) { - await DataSourcesService.addExtraData(dataSource, options.retrieveDataComponents); + await this.addExtraData(dataSource, options.retrieveDataComponents); return dataSource; } else { return null; From 88e245926c319475437d93de6acda83360548a9a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:11:36 -0500 Subject: [PATCH 120/370] refactor: enable lifecycle hooks and event-driven arch in dc and ds services - DataComponentsService now uses beforeCreate to build outbound embedded rel for x_mitre_data_source_ref - while DataSourcesServices uses an eventListener to build an inbound embedded rel. - Tested and working for embedded relationship adds and removals. - Also refactors the DC controller to use the new global service exception handler Additional refactors are needed, but this change ensures the DC + DS regression tests pass now. --- app/controllers/data-components-controller.js | 53 ++-- app/lib/event-constants.js | 4 + app/services/stix/data-components-service.js | 274 ++++++++++++++++ app/services/stix/data-sources-service.js | 299 +++++++++++++----- 4 files changed, 511 insertions(+), 119 deletions(-) diff --git a/app/controllers/data-components-controller.js b/app/controllers/data-components-controller.js index 37ecfb8b..44867526 100644 --- a/app/controllers/data-components-controller.js +++ b/app/controllers/data-components-controller.js @@ -2,9 +2,8 @@ const dataComponentsService = require('../services/stix/data-components-service'); const logger = require('../lib/logger'); -const { DuplicateIdError } = require('../exceptions'); -exports.retrieveAll = async function (req, res) { +exports.retrieveAll = async function (req, res, next) { const options = { offset: req.query.offset || 0, limit: req.query.limit || 0, @@ -27,12 +26,11 @@ exports.retrieveAll = async function (req, res) { } return res.status(200).send(results); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data components. Server error.'); + next(err); } }; -exports.retrieveById = async function (req, res) { +exports.retrieveById = async function (req, res, next) { const options = { versions: req.query.versions || 'latest', }; @@ -48,12 +46,11 @@ exports.retrieveById = async function (req, res) { return res.status(200).send(dataComponents); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data component. Server error.'); + next(err); } }; -exports.retrieveChannelsById = async function (req, res) { +exports.retrieveChannelsById = async function (req, res, next) { try { const dataComponents = await dataComponentsService.retrieveById(req.params.stixId, { versions: 'latest', @@ -68,12 +65,11 @@ exports.retrieveChannelsById = async function (req, res) { return res.status(200).send(channels); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data components. Server error.'); + next(err); } }; -exports.retrieveLogSourcesById = async function (req, res) { +exports.retrieveLogSourcesById = async function (req, res, next) { try { const dataComponents = await dataComponentsService.retrieveById(req.params.stixId, { versions: 'latest', @@ -85,12 +81,11 @@ exports.retrieveLogSourcesById = async function (req, res) { return res.status(200).send(dataComponents[0].stix.x_mitre_log_sources); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data components. Server error.'); + next(err); } }; -exports.retrieveVersionById = async function (req, res) { +exports.retrieveVersionById = async function (req, res, next) { try { const dataComponent = await dataComponentsService.retrieveVersionById( req.params.stixId, @@ -103,12 +98,11 @@ exports.retrieveVersionById = async function (req, res) { return res.status(200).send(dataComponent); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data component. Server error.'); + next(err); } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { // Get the data from the request const dataComponentData = req.body; const options = { @@ -122,19 +116,11 @@ exports.create = async function (req, res) { logger.debug('Success: Created data component with id ' + dataComponent.stix.id); return res.status(201).send(dataComponent); } catch (err) { - if (err instanceof DuplicateIdError) { - logger.warn('Duplicate stix.id and stix.modified'); - return res - .status(409) - .send('Unable to create data component. Duplicate stix.id and stix.modified properties.'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create data component. Server error.'); - } + next(err); } }; -exports.updateFull = async function (req, res) { +exports.updateFull = async function (req, res, next) { // Get the data from the request const dataComponentData = req.body; @@ -153,12 +139,11 @@ exports.updateFull = async function (req, res) { return res.status(200).send(dataComponent); } } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update data component. Server error.'); + next(err); } }; -exports.deleteVersionById = async function (req, res) { +exports.deleteVersionById = async function (req, res, next) { try { const dataComponent = await dataComponentsService.deleteVersionById( req.params.stixId, @@ -171,12 +156,11 @@ exports.deleteVersionById = async function (req, res) { return res.status(204).end(); } } catch (err) { - logger.error('Delete data component failed. ' + err); - return res.status(500).send('Unable to delete data component. Server error.'); + next(err); } }; -exports.deleteById = async function (req, res) { +exports.deleteById = async function (req, res, next) { try { const dataComponents = await dataComponentsService.deleteById(req.params.stixId); if (dataComponents.deletedCount === 0) { @@ -186,7 +170,6 @@ exports.deleteById = async function (req, res) { return res.status(204).end(); } } catch (err) { - logger.error('Delete data component failed. ' + err); - return res.status(500).send('Unable to delete data component. Server error.'); + next(err); } }; diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index b5a25d7a..2b37004c 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -119,6 +119,10 @@ module.exports = Object.freeze({ ANALYTIC_DATA_COMPONENTS_REFERENCED: 'x-mitre-analytic::data-components-referenced', ANALYTIC_DATA_COMPONENTS_REMOVED: 'x-mitre-analytic::data-components-removed', + // Data Component - Data Source relationship + DATA_COMPONENT_DATA_SOURCE_REFERENCED: 'x-mitre-data-component::data-source-referenced', + DATA_COMPONENT_DATA_SOURCE_REMOVED: 'x-mitre-data-component::data-source-removed', + // Technique - Sub-technique conversion TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE: 'attack-pattern::converted-to-subtechnique', SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE: 'attack-pattern::converted-to-technique', diff --git a/app/services/stix/data-components-service.js b/app/services/stix/data-components-service.js index 93d41883..545b0073 100644 --- a/app/services/stix/data-components-service.js +++ b/app/services/stix/data-components-service.js @@ -1,17 +1,29 @@ 'use strict'; const dataComponentsRepository = require('../../repository/data-components-repository'); +const dataSourcesRepository = require('../../repository/data-sources-repository'); const { BaseService } = require('../meta-classes'); const { DataComponent: DataComponentType } = require('../../lib/types'); const EventBus = require('../../lib/event-bus'); const logger = require('../../lib/logger'); +const Exceptions = require('../../exceptions'); /** * Service for managing data components * + * Lifecycle hooks: + * - beforeCreate: Builds outbound embedded_relationship for x_mitre_data_source_ref and validates it exists + * - afterCreate: Emits domain event to notify DataSourcesService + * - beforeUpdate: Rebuilds outbound embedded_relationship, validates data source reference, and detects changes + * - afterUpdate: Emits domain events for added/removed data source references + * * Event listeners: * - x-mitre-analytic::data-components-referenced - Add inbound relationships when analytic references data components * - x-mitre-analytic::data-components-removed - Remove inbound relationships when analytic removes data components + * + * Events emitted (listened to by DataSourcesService): + * - x-mitre-data-component::data-source-referenced + * - x-mitre-data-component::data-source-removed */ class DataComponentsService extends BaseService { /** @@ -142,6 +154,268 @@ class DataComponentsService extends BaseService { } } } + + /** + * Lifecycle hook: Prepare data component data before database persistence + * - Builds outbound embedded_relationship for data source reference + * - Validates that the referenced data source exists + * - Detects if this is a new version and tracks removed relationships + * + * @param {Object} data - The data component data to be created + * @param {Object} data.stix - STIX properties + * @param {string} data.stix.x_mitre_data_source_ref - Data source STIX ID reference + * @param {Object} data.workspace - Workbench metadata + * @param {Object} options - Creation options + * @throws {Exceptions.NotFoundError} If the referenced data source does not exist + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + async beforeCreate(data, options) { + // Initialize embedded_relationships if not present + if (!data.workspace) { + data.workspace = {}; + } + if (!data.workspace.embedded_relationships) { + data.workspace.embedded_relationships = []; + } + + // Check if this is a new version of an existing data component + // (same stix.id, but creating a new version with different modified date) + let previousVersion = null; + if (data.stix?.id) { + try { + previousVersion = await dataComponentsRepository.retrieveLatestByStixId(data.stix.id); + } catch { + // It's okay if there's no previous version - this might be the first version + logger.debug(`No previous version found for data component ${data.stix.id}`); + } + } + + // Build outbound embedded_relationship for data source reference + // Cross-repository READS are allowed for denormalization (see EVENT_BUS_ARCHITECTURE.md) + const newDataSourceRef = data.stix?.x_mitre_data_source_ref; + const oldDataSourceRef = previousVersion?.stix?.x_mitre_data_source_ref; + + // Detect changes for event emission + if (previousVersion) { + if (oldDataSourceRef && !newDataSourceRef) { + // Data source reference was removed in this version + this._removedDataSourceRef = oldDataSourceRef; + } else if (!oldDataSourceRef && newDataSourceRef) { + // Data source reference was added in this version + this._addedDataSourceRef = newDataSourceRef; + } else if (oldDataSourceRef && newDataSourceRef && oldDataSourceRef !== newDataSourceRef) { + // Data source reference changed + this._removedDataSourceRef = oldDataSourceRef; + this._addedDataSourceRef = newDataSourceRef; + } + } + + if (newDataSourceRef) { + const dataSource = await dataSourcesRepository.retrieveLatestByStixId(newDataSourceRef); + if (!dataSource) { + throw new Exceptions.NotFoundError({ + objectType: 'x-mitre-data-source', + objectId: newDataSourceRef, + message: `Cannot create data component: Referenced data source ${newDataSourceRef} does not exist`, + }); + } + + // Add outbound embedded_relationship + data.workspace.embedded_relationships.push({ + stix_id: newDataSourceRef, + attack_id: dataSource.workspace?.attack_id || null, + name: dataSource.stix?.name || null, + direction: 'outbound', + }); + + logger.debug( + `Built outbound embedded relationship for data component to data source ${newDataSourceRef}`, + ); + } + } + + /** + * Lifecycle hook: Handle post-creation side effects + * Emits domain events to notify DataSourcesService about referenced/removed data sources + * This handles both first-time creation and new version creation (versioning) + * + * @param {Object} createdDocument - The created data component document + * @param {Object} _options - Creation options (unused) + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + async afterCreate(createdDocument, _options) { + const addedRef = this._addedDataSourceRef; + const removedRef = this._removedDataSourceRef; + + // Emit event for newly referenced data source + if (addedRef) { + logger.info( + `DataComponentsService: Emitting data-source-referenced event for data source ${addedRef}`, + { stixId: createdDocument.stix.id, dataSourceId: addedRef }, + ); + + await EventBus.emit('x-mitre-data-component::data-source-referenced', { + dataComponentId: createdDocument.stix.id, + dataComponent: createdDocument.toObject ? createdDocument.toObject() : createdDocument, + dataSourceId: addedRef, + }); + } + + // Emit event for removed data source (when creating a new version without the reference) + if (removedRef) { + logger.info( + `DataComponentsService: Emitting data-source-removed event for data source ${removedRef}`, + { stixId: createdDocument.stix.id, dataSourceId: removedRef }, + ); + + await EventBus.emit('x-mitre-data-component::data-source-removed', { + dataComponentId: createdDocument.stix.id, + dataSourceId: removedRef, + }); + } + + // If no changes detected but there is a current reference, emit referenced event + // (this handles the case where this is the first version being created) + const currentDataSourceRef = createdDocument.stix?.x_mitre_data_source_ref; + if (!addedRef && !removedRef && currentDataSourceRef) { + logger.info( + `DataComponentsService: Emitting data-source-referenced event for data source ${currentDataSourceRef}`, + { stixId: createdDocument.stix.id, dataSourceId: currentDataSourceRef }, + ); + + await EventBus.emit('x-mitre-data-component::data-source-referenced', { + dataComponentId: createdDocument.stix.id, + dataComponent: createdDocument.toObject ? createdDocument.toObject() : createdDocument, + dataSourceId: currentDataSourceRef, + }); + } + + // Clean up instance variables + delete this._addedDataSourceRef; + delete this._removedDataSourceRef; + } + + /** + * Lifecycle hook: Prepare data component data before update persistence + * - Rebuilds outbound embedded_relationship for data source + * - Preserves inbound relationships from analytics + * - Validates that the referenced data source exists + * - Detects changes in data source reference for event emission + * + * @param {string} stixId - STIX ID of the data component being updated + * @param {string} stixModified - Modified timestamp of the version being updated + * @param {Object} data - Updated data component data + * @param {Object} existingDocument - The existing data component document + * @throws {Exceptions.NotFoundError} If the referenced data source does not exist + * @returns {Promise} + */ + async beforeUpdate(stixId, stixModified, data, existingDocument) { + // Initialize embedded_relationships if not present + if (!data.workspace) { + data.workspace = {}; + } + if (!data.workspace.embedded_relationships) { + data.workspace.embedded_relationships = []; + } + + // Validate that the referenced data source exists and build outbound relationship + const newDataSourceRef = data.stix?.x_mitre_data_source_ref; + + // Preserve existing non-data-source embedded relationships (e.g., inbound from analytics) + const existingNonDataSourceRels = (data.workspace.embedded_relationships || []).filter( + (rel) => !rel.stix_id?.startsWith('x-mitre-data-source--'), + ); + + // Build new outbound embedded relationship for data source + const dataSourceEmbeddedRel = []; + if (newDataSourceRef) { + const dataSource = await dataSourcesRepository.retrieveLatestByStixId(newDataSourceRef); + if (!dataSource) { + throw new Exceptions.NotFoundError({ + objectType: 'x-mitre-data-source', + objectId: newDataSourceRef, + message: `Cannot update data component: Referenced data source ${newDataSourceRef} does not exist`, + }); + } + + // Add outbound embedded_relationship + dataSourceEmbeddedRel.push({ + stix_id: newDataSourceRef, + attack_id: dataSource.workspace?.attack_id || null, + name: dataSource.stix?.name || null, + direction: 'outbound', + }); + } + + // Rebuild embedded_relationships: preserve inbound relationships, rebuild outbound data source relationship + data.workspace.embedded_relationships = [ + ...existingNonDataSourceRels, + ...dataSourceEmbeddedRel, + ]; + + // Detect changes in data source reference for event emission + const oldDataSourceRef = existingDocument.stix?.x_mitre_data_source_ref; + + // Determine what changed + if (oldDataSourceRef && !newDataSourceRef) { + // Data source removed (set to null/undefined) + this._removedDataSourceRef = oldDataSourceRef; + } else if (!oldDataSourceRef && newDataSourceRef) { + // Data source added + this._addedDataSourceRef = newDataSourceRef; + } else if (oldDataSourceRef && newDataSourceRef && oldDataSourceRef !== newDataSourceRef) { + // Data source changed + this._removedDataSourceRef = oldDataSourceRef; + this._addedDataSourceRef = newDataSourceRef; + } + } + + /** + * Lifecycle hook: Handle post-update side effects + * Emits domain events for added/removed data source references + * + * @param {Object} updatedDocument - The updated data component document + * @param {Object} _previousDocument - The previous version of the data component (unused) + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + async afterUpdate(updatedDocument, _previousDocument) { + const addedRef = this._addedDataSourceRef; + const removedRef = this._removedDataSourceRef; + + // Emit event for newly referenced data source + if (addedRef) { + logger.info( + `DataComponentsService: Emitting data-source-referenced event for data source ${addedRef}`, + { stixId: updatedDocument.stix.id, dataSourceId: addedRef }, + ); + + await EventBus.emit('x-mitre-data-component::data-source-referenced', { + dataComponentId: updatedDocument.stix.id, + dataComponent: updatedDocument.toObject ? updatedDocument.toObject() : updatedDocument, + dataSourceId: addedRef, + }); + } + + // Emit event for removed data source + if (removedRef) { + logger.info( + `DataComponentsService: Emitting data-source-removed event for data source ${removedRef}`, + { stixId: updatedDocument.stix.id, dataSourceId: removedRef }, + ); + + await EventBus.emit('x-mitre-data-component::data-source-removed', { + dataComponentId: updatedDocument.stix.id, + dataSourceId: removedRef, + }); + } + + // Clean up instance variables + delete this._addedDataSourceRef; + delete this._removedDataSourceRef; + } } DataComponentsService.initializeEventListeners(); diff --git a/app/services/stix/data-sources-service.js b/app/services/stix/data-sources-service.js index 477cfce9..0bf7fa57 100644 --- a/app/services/stix/data-sources-service.js +++ b/app/services/stix/data-sources-service.js @@ -1,118 +1,249 @@ 'use strict'; const dataSourcesRepository = require('../../repository/data-sources-repository'); -const dataComponentsService = require('./data-components-service'); +const dataComponentsRepository = require('../../repository/data-components-repository'); const { BaseService } = require('../meta-classes'); const { DataSource: DataSourceType } = require('../../lib/types'); -const { - MissingParameterError, - BadlyFormattedParameterError, - InvalidQueryStringParameterError, -} = require('../../exceptions'); - +const EventBus = require('../../lib/event-bus'); +const logger = require('../../lib/logger'); + +/** + * Service for managing data sources + * + * Event listeners: + * - x-mitre-data-component::data-source-referenced - Add inbound relationships when data component references data source + * - x-mitre-data-component::data-source-removed - Remove inbound relationships when data component removes data source reference + * + * The retrieveDataComponents query parameter is handled by building the relationship + * from workspace.embedded_relationships which are maintained via the event-driven architecture. + */ class DataSourcesService extends BaseService { - errors = { - missingParameter: 'Missing required parameter', - badlyFormattedParameter: 'Badly formatted parameter', - duplicateId: 'Duplicate id', - notFound: 'Document not found', - invalidQueryStringParameter: 'Invalid query string parameter', - }; - - async addExtraData(dataSource, retrieveDataComponents) { - await this.addCreatedByAndModifiedByIdentities(dataSource); - if (retrieveDataComponents) { - await this.addDataComponents(dataSource); - } - } - - async addExtraDataToAll(dataSources, retrieveDataComponents) { - for (const dataSource of dataSources) { - await this.addExtraData(dataSource, retrieveDataComponents); - } - } + /** + * Initialize event listeners + * Called once on app startup + */ + static initializeEventListeners() { + EventBus.on( + 'x-mitre-data-component::data-source-referenced', + this.handleDataSourceReferenced.bind(this), + ); - async addDataComponents(dataSource) { - // We have to work with the latest version of all data components to avoid mishandling a situation - // where an earlier version of a data component may reference a data source, but the latest - // version doesn't. + EventBus.on( + 'x-mitre-data-component::data-source-removed', + this.handleDataSourceRemoved.bind(this), + ); - // Retrieve the latest version of all data components - const allDataComponents = await dataComponentsService.retrieveAll({ - includeDeprecated: true, - includeRevoked: true, - }); + logger.info('DataSourcesService: Event listeners initialized'); + } - // Add the data components that reference the data source - dataSource.dataComponents = allDataComponents.filter( - (dataComponent) => dataComponent.stix.x_mitre_data_source_ref === dataSource.stix.id, + /** + * Handle data source being referenced by a data component + * Add inbound embedded_relationship + * + * @param {Object} payload - Event payload + * @param {Object} payload.dataComponent - The data component document that references the data source + * @param {string} payload.dataSourceId - Data source STIX ID being referenced + * @returns {Promise} + */ + static async handleDataSourceReferenced(payload) { + const { dataComponent, dataSourceId } = payload; + + logger.info( + `DataSourcesService heard event: 'x-mitre-data-component::data-source-referenced' for ${dataComponent.stix.id}`, ); - } - async retrieveById(stixId, options) { try { - // versions=all Retrieve all versions of the data source with the stixId - // versions=latest Retrieve the data source with the latest modified date for this stixId + const dataSource = await dataSourcesRepository.retrieveLatestByStixId(dataSourceId); - if (!stixId) { - throw new MissingParameterError('stixId'); + if (!dataSource) { + logger.warn( + `DataSourcesService: Could not find data source ${dataSourceId} to add inbound relationship`, + ); + return; } - if (options.versions === 'all') { - const dataSources = await this.repository.retrieveAllById(stixId); - await this.addExtraDataToAll(dataSources, options.retrieveDataComponents); - return dataSources; - } else if (options.versions === 'latest') { - const dataSource = await this.repository.retrieveLatestByStixId(stixId); - - // Note: document is null if not found - if (dataSource) { - await this.addExtraData(dataSource, options.retrieveDataComponents); - return [dataSource]; - } else { - return []; - } - } else { - throw new InvalidQueryStringParameterError(); + // Initialize embedded_relationships if needed + if (!dataSource.workspace) { + dataSource.workspace = {}; } - } catch (err) { - if (err.name === 'CastError') { - throw new BadlyFormattedParameterError(); - } else { - throw err; + if (!dataSource.workspace.embedded_relationships) { + dataSource.workspace.embedded_relationships = []; } + + // Check if relationship already exists + const exists = dataSource.workspace.embedded_relationships.some( + (rel) => rel.stix_id === dataComponent.stix.id && rel.direction === 'inbound', + ); + + if (!exists) { + // Add inbound embedded_relationship + dataSource.workspace.embedded_relationships.push({ + stix_id: dataComponent.stix.id, + attack_id: dataComponent.workspace?.attack_id || null, + name: dataComponent.stix.name, + direction: 'inbound', + }); + + logger.info( + `DataSourcesService: Added inbound relationship from data component ${dataComponent.stix.id} to data source ${dataSourceId}`, + ); + } + + await dataSourcesRepository.saveDocument(dataSource); + } catch (error) { + logger.error( + `DataSourcesService: Error handling data-source-referenced for ${dataSourceId}:`, + error, + ); } } - async retrieveVersionById(stixId, modified, options) { + /** + * Handle data source being removed from a data component + * Remove inbound embedded_relationship + * + * @param {Object} payload - Event payload + * @param {string} payload.dataComponentId - STIX ID of the data component + * @param {string} payload.dataSourceId - Data source STIX ID being removed + * @returns {Promise} + */ + static async handleDataSourceRemoved(payload) { + const { dataComponentId, dataSourceId } = payload; + + logger.info( + `DataSourcesService heard event: 'x-mitre-data-component::data-source-removed' for data component ${dataComponentId}`, + ); + try { - // Retrieve the version of the data source with the matching stixId and modified date + const dataSource = await dataSourcesRepository.retrieveLatestByStixId(dataSourceId); - if (!stixId) { - throw new MissingParameterError('stixId'); + if (!dataSource) { + logger.warn( + `DataSourcesService: Could not find data source ${dataSourceId} to remove inbound relationship`, + ); + return; } - if (!modified) { - throw new MissingParameterError('modified'); + if (dataSource.workspace?.embedded_relationships) { + // Remove inbound embedded_relationship + const initialLength = dataSource.workspace.embedded_relationships.length; + dataSource.workspace.embedded_relationships = + dataSource.workspace.embedded_relationships.filter( + (rel) => !(rel.stix_id === dataComponentId && rel.direction === 'inbound'), + ); + + const removed = dataSource.workspace.embedded_relationships.length < initialLength; + if (removed) { + logger.info( + `DataSourcesService: Removed inbound relationship from data component ${dataComponentId} to data source ${dataSourceId}`, + ); + } } - const dataSource = await this.repository.retrieveOneByVersion(stixId, modified); + await dataSourcesRepository.saveDocument(dataSource); + } catch (error) { + logger.error( + `DataSourcesService: Error handling data-source-removed for ${dataSourceId}:`, + error, + ); + } + } + + /** + * Retrieve data sources by STIX ID + * If retrieveDataComponents is true, populate dataComponents array from embedded_relationships + * + * @param {string} stixId - The STIX ID of the data source + * @param {Object} options - Query options + * @param {string} [options.versions='latest'] - Which versions to retrieve + * @param {boolean} [options.retrieveDataComponents=false] - Include related data components + * @returns {Promise} Array of data source versions + */ + async retrieveById(stixId, options) { + const dataSources = await super.retrieveById(stixId, options); - // Note: document is null if not found - if (dataSource) { - await this.addExtraData(dataSource, options.retrieveDataComponents); - return dataSource; - } else { - return null; + // If retrieveDataComponents is requested, build the dataComponents array from embedded_relationships + if (options.retrieveDataComponents && dataSources.length > 0) { + for (const dataSource of dataSources) { + await this.populateDataComponents(dataSource); } - } catch (err) { - if (err.name === 'CastError') { - throw new BadlyFormattedParameterError(); - } else { - throw err; + } + + return dataSources; + } + + /** + * Retrieve a specific version of a data source + * If retrieveDataComponents is true, populate dataComponents array from embedded_relationships + * + * @param {string} stixId - The STIX ID of the data source + * @param {string} modified - The modified timestamp of the version + * @param {Object} options - Query options + * @param {boolean} [options.retrieveDataComponents=false] - Include related data components + * @returns {Promise} The data source document or null + */ + async retrieveVersionById(stixId, modified, options) { + const dataSource = await super.retrieveVersionById(stixId, modified); + + // If retrieveDataComponents is requested, build the dataComponents array from embedded_relationships + if (options.retrieveDataComponents && dataSource) { + await this.populateDataComponents(dataSource); + } + + return dataSource; + } + + /** + * Populate the dataComponents array on a data source from its embedded_relationships + * This retrieves the full data component documents for each inbound relationship + * + * @param {Object} dataSource - The data source document + * @returns {Promise} + */ + async populateDataComponents(dataSource) { + // Get inbound data component relationships from embedded_relationships + const dataComponentRels = + dataSource.workspace?.embedded_relationships?.filter( + (rel) => rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-data-component--'), + ) || []; + + // Fetch the full data component documents + const dataComponents = []; + for (const rel of dataComponentRels) { + try { + const dataComponent = await dataComponentsRepository.retrieveLatestByStixId(rel.stix_id); + if (dataComponent) { + // Add identity information to data component + await this.addCreatedByAndModifiedByIdentities(dataComponent); + dataComponents.push(dataComponent.toObject ? dataComponent.toObject() : dataComponent); + } else { + logger.warn( + `DataSourcesService: Could not find data component ${rel.stix_id} referenced in embedded_relationships`, + ); + } + } catch (error) { + logger.error(`DataSourcesService: Error fetching data component ${rel.stix_id}:`, error); } } + + // Attach the populated dataComponents array to the data source + // This is a transient property, not persisted to the database + if (dataSource.toObject) { + // For Mongoose documents + const obj = dataSource.toObject(); + obj.dataComponents = dataComponents; + Object.assign(dataSource, obj); + } else { + // For plain objects + dataSource.dataComponents = dataComponents; + } + + logger.debug( + `Populated ${dataComponents.length} data component(s) for data source ${dataSource.stix.id}`, + ); } } +DataSourcesService.initializeEventListeners(); + module.exports = new DataSourcesService(DataSourceType, dataSourcesRepository); From 45f895b7b2d00b6e94ef0225cfc0b763dad5b330 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:39:46 -0500 Subject: [PATCH 121/370] fix: stop cross-referencing user-editable name field in embedded relationships - Workspace model schema updated to remove name field - All services updated to stop writing name to embedded_relationships - On-demand name lookup implemented in AnalyticsService for special case Prevents data staleness issues and aligning and aligning with MongoDBs recommendations against arrays with duplicated mutable data. --- app/models/subschemas/workspace.js | 5 +- app/services/stix/analytics-service.js | 83 ++++++++++++++++++- app/services/stix/data-components-service.js | 3 - app/services/stix/data-sources-service.js | 1 - .../stix/detection-strategies-service.js | 1 - 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/app/models/subschemas/workspace.js b/app/models/subschemas/workspace.js index a1f2fd8d..360fca63 100644 --- a/app/models/subschemas/workspace.js +++ b/app/models/subschemas/workspace.js @@ -10,8 +10,9 @@ const collectionVersionSchema = new mongoose.Schema(collectionVersion, { _id: fa const embedddedRelationship = { stix_id: { type: String, required: true }, - attack_id: String, - name: String, + attack_id: String, // Immutable, server-generated identifier - safe to denormalize + // Note: 'name' field removed - names are mutable and should be fetched on read + // Services that need names should fetch the full document using stix_id direction: { type: String, // inbound: The embedded relationship points TO this document (I am referenced) diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index 13803210..c5a7f8f1 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -258,7 +258,6 @@ class AnalyticsService extends BaseService { data.workspace.embedded_relationships.push({ stix_id: dataComponentId, attack_id: dataComponent.workspace?.attack_id || null, - name: dataComponent.stix?.name || null, direction: 'outbound', }); } @@ -350,7 +349,6 @@ class AnalyticsService extends BaseService { dataComponentEmbeddedRels.push({ stix_id: dataComponentId, attack_id: dataComponent.workspace?.attack_id || null, - name: dataComponent.stix?.name || null, direction: 'outbound', }); } @@ -457,6 +455,7 @@ class AnalyticsService extends BaseService { /** * Retrieve all analytics with optional filtering and pagination * Strips embedded_relationships from response unless explicitly requested + * When embedded_relationships are included, populates names for detection strategies * * @param {Object} options - Query options * @param {boolean} [options.includeEmbeddedRelationships=false] - Include embedded relationships in response @@ -466,7 +465,12 @@ class AnalyticsService extends BaseService { async retrieveAll(options) { const results = await super.retrieveAll(options); - if (!options.includeEmbeddedRelationships) { + if (options.includeEmbeddedRelationships) { + // Populate names for embedded relationships + const analytics = options.includePagination ? results.data : results; + await this.populateEmbeddedRelationshipNames(analytics); + } else { + // Strip embedded_relationships from response if (options.includePagination) { await this.stripEmbeddedRelationships(results.data); } else { @@ -480,6 +484,7 @@ class AnalyticsService extends BaseService { /** * Retrieve analytics by STIX ID * Strips embedded_relationships from response unless explicitly requested + * When embedded_relationships are included, populates names for detection strategies * * @param {string} stixId - The STIX ID of the analytic * @param {Object} options - Query options @@ -489,13 +494,83 @@ class AnalyticsService extends BaseService { async retrieveById(stixId, options) { const results = await super.retrieveById(stixId, options); - if (!options.includeEmbeddedRelationships) { + if (options.includeEmbeddedRelationships) { + // Populate names for embedded relationships + await this.populateEmbeddedRelationshipNames(results); + } else { + // Strip embedded_relationships from response await this.stripEmbeddedRelationships(results); } return results; } + /** + * Populate names for embedded relationships by fetching referenced documents + * This is needed because names are no longer stored in embedded_relationships (only stix_id + attack_id) + * Handles both inbound detection strategy relationships and outbound data component relationships + * + * @param {Array} analytics - Array of analytic documents + * @returns {Promise} + */ + async populateEmbeddedRelationshipNames(analytics) { + const detectionStrategiesRepository = require('../../repository/detection-strategies-repository'); + const dataComponentsRepository = require('../../repository/data-components-repository'); + + for (const analytic of analytics) { + if (!analytic.workspace?.embedded_relationships) { + continue; + } + + for (const rel of analytic.workspace.embedded_relationships) { + // Handle inbound relationships from detection strategies + if (rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--')) { + try { + const detectionStrategy = + await detectionStrategiesRepository.retrieveLatestByStixId(rel.stix_id); + if (detectionStrategy) { + // Add name as a transient property (not persisted to DB) + rel.name = detectionStrategy.stix.name; + } else { + logger.warn( + `AnalyticsService: Could not find detection strategy ${rel.stix_id} to populate name`, + ); + rel.name = null; + } + } catch (error) { + logger.error( + `AnalyticsService: Error fetching detection strategy ${rel.stix_id} for name:`, + error, + ); + rel.name = null; + } + } + + // Handle outbound relationships to data components + if (rel.direction === 'outbound' && rel.stix_id?.startsWith('x-mitre-data-component--')) { + try { + const dataComponent = await dataComponentsRepository.retrieveLatestByStixId(rel.stix_id); + if (dataComponent) { + // Add name as a transient property (not persisted to DB) + rel.name = dataComponent.stix.name; + } else { + logger.warn( + `AnalyticsService: Could not find data component ${rel.stix_id} to populate name`, + ); + rel.name = null; + } + } catch (error) { + logger.error( + `AnalyticsService: Error fetching data component ${rel.stix_id} for name:`, + error, + ); + rel.name = null; + } + } + } + } + } + /** * Remove embedded_relationships from analytics response * Used to hide internal relationship metadata from API consumers diff --git a/app/services/stix/data-components-service.js b/app/services/stix/data-components-service.js index 545b0073..3affb2c6 100644 --- a/app/services/stix/data-components-service.js +++ b/app/services/stix/data-components-service.js @@ -85,7 +85,6 @@ class DataComponentsService extends BaseService { dataComponent.workspace.embedded_relationships.push({ stix_id: analytic.stix.id, attack_id: analytic.workspace?.attack_id || null, - name: analytic.stix.name, direction: 'inbound', }); @@ -225,7 +224,6 @@ class DataComponentsService extends BaseService { data.workspace.embedded_relationships.push({ stix_id: newDataSourceRef, attack_id: dataSource.workspace?.attack_id || null, - name: dataSource.stix?.name || null, direction: 'outbound', }); @@ -344,7 +342,6 @@ class DataComponentsService extends BaseService { dataSourceEmbeddedRel.push({ stix_id: newDataSourceRef, attack_id: dataSource.workspace?.attack_id || null, - name: dataSource.stix?.name || null, direction: 'outbound', }); } diff --git a/app/services/stix/data-sources-service.js b/app/services/stix/data-sources-service.js index 0bf7fa57..df8a8d5c 100644 --- a/app/services/stix/data-sources-service.js +++ b/app/services/stix/data-sources-service.js @@ -80,7 +80,6 @@ class DataSourcesService extends BaseService { dataSource.workspace.embedded_relationships.push({ stix_id: dataComponent.stix.id, attack_id: dataComponent.workspace?.attack_id || null, - name: dataComponent.stix.name, direction: 'inbound', }); diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index bf1e299e..13e94c4d 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -53,7 +53,6 @@ class DetectionStrategiesService extends BaseService { data.workspace.embedded_relationships.push({ stix_id: analyticId, attack_id: analytic?.workspace?.attack_id || null, - name: analytic?.stix?.name || null, direction: 'outbound', }); } From 699622ca92387dcbb53b0c05a79cb3d6c29fa9d9 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:42:40 -0500 Subject: [PATCH 122/370] fix(collection-bundles): sort objects by dependencies during bundle import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensures dependencies are created before objects that reference them. Prevents NotFoundError when data components are imported before their referenced data sources, or when analytics are imported before their referenced data components. Added sortObjectsByDependencies() to order objects by type before processing: identities → data sources → data components → analytics → detection strategies → other objects → relationships. --- .../import-bundle.js | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/app/services/stix/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js index 2b406e19..90778a02 100644 --- a/app/services/stix/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -250,6 +250,45 @@ async function processStixObject( } } +/** + * Sort objects to ensure dependencies are created before objects that reference them + * Dependency order: + * 1. Data sources must be created before data components + * 2. Data components must be created before analytics + * 3. Analytics must be created before detection strategies + * @param {Array} objects - Array of STIX objects to sort + * @returns {Array} Sorted array of STIX objects + */ +function sortObjectsByDependencies(objects) { + // Define dependency order (lower numbers are created first) + const typeOrder = { + [types.MarkingDefinition]: 0, + [types.Identity]: 1, + [types.DataSource]: 2, // Must come before data components + [types.DataComponent]: 3, // Must come before analytics + [types.Analytic]: 4, // Must come before detection strategies + [types.DetectionStrategy]: 5, + [types.Technique]: 6, + [types.Tactic]: 7, + [types.Mitigation]: 8, + [types.Group]: 9, + [types.Campaign]: 10, + [types.Malware]: 11, + [types.Tool]: 12, + [types.Asset]: 13, + [types.Matrix]: 14, + [types.Relationship]: 15, // Relationships last + [types.Note]: 16, + [types.Collection]: 17, + }; + + return objects.slice().sort((a, b) => { + const orderA = typeOrder[a.type] ?? 100; // Unknown types go last + const orderB = typeOrder[b.type] ?? 100; + return orderA - orderB; + }); +} + /** * Process all objects in the bundle * @param {Array} objects - Array of STIX objects to process @@ -269,7 +308,10 @@ async function processObjects( importReferences, referenceImportResults, ) { - for (const importObject of objects) { + // Sort objects by dependencies before processing + const sortedObjects = sortObjectsByDependencies(objects); + + for (const importObject of sortedObjects) { // Check if object is in x_mitre_contents if ( !contentsMap.delete(makeKeyFromObject(importObject)) && From 858e99ee6210f6d7cec69d34c88ef83bfd0162d1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:42:55 -0500 Subject: [PATCH 123/370] style: apply formatting --- app/services/stix/analytics-service.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index c5a7f8f1..c3dc3f5b 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -524,10 +524,14 @@ class AnalyticsService extends BaseService { for (const rel of analytic.workspace.embedded_relationships) { // Handle inbound relationships from detection strategies - if (rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--')) { + if ( + rel.direction === 'inbound' && + rel.stix_id?.startsWith('x-mitre-detection-strategy--') + ) { try { - const detectionStrategy = - await detectionStrategiesRepository.retrieveLatestByStixId(rel.stix_id); + const detectionStrategy = await detectionStrategiesRepository.retrieveLatestByStixId( + rel.stix_id, + ); if (detectionStrategy) { // Add name as a transient property (not persisted to DB) rel.name = detectionStrategy.stix.name; @@ -549,7 +553,9 @@ class AnalyticsService extends BaseService { // Handle outbound relationships to data components if (rel.direction === 'outbound' && rel.stix_id?.startsWith('x-mitre-data-component--')) { try { - const dataComponent = await dataComponentsRepository.retrieveLatestByStixId(rel.stix_id); + const dataComponent = await dataComponentsRepository.retrieveLatestByStixId( + rel.stix_id, + ); if (dataComponent) { // Add name as a transient property (not persisted to DB) rel.name = dataComponent.stix.name; From 19148318f2c192c39c26e15264e4c16f02137642 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:43:35 -0500 Subject: [PATCH 124/370] docs: add documentation about stix and mongodb document versioning --- STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md | 613 ++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md diff --git a/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md b/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md new file mode 100644 index 00000000..c39ca9e4 --- /dev/null +++ b/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md @@ -0,0 +1,613 @@ +# STIX Versioning and Embedded Relationships + +## Overview + +This document explains how STIX versioning works in the ATT&CK Workbench REST API, and how it interacts with the embedded relationships feature. Understanding these concepts is critical for developers working with the event-driven architecture. + +--- + +## STIX Versioning: POST vs PUT + +The ATT&CK Workbench implements STIX 2.1 versioning semantics with two distinct update mechanisms: + +### POST - Creating New Versions (Versioned History) + +**Endpoint:** `POST /api/{type}` + +**Behavior:** +- Creates a **new Mongoose document** in the database +- Used when `stix.id` already exists but `stix.modified` is different +- Builds a **temporal version chain** of immutable snapshots +- Each version is a complete, independent document +- Versions are linked by sharing the same `stix.id` + +**Example:** +```javascript +// First version +POST /api/data-components +{ + stix: { + id: "x-mitre-data-component--123", + modified: "2024-01-01T00:00:00.000Z", + name: "Process Creation" + } +} + +// Second version (creates NEW document) +POST /api/data-components +{ + stix: { + id: "x-mitre-data-component--123", // Same ID + modified: "2024-02-01T00:00:00.000Z", // Different modified + name: "Process Creation Events" // Updated content + } +} +``` + +**Result:** Two separate Mongoose documents in the database: +1. Document with `modified: "2024-01-01T00:00:00.000Z"` (unchanged) +2. Document with `modified: "2024-02-01T00:00:00.000Z"` (new) + +**Use Case:** +- **Primary update mechanism** in the Workbench frontend +- Preserves complete edit history +- Enables rollback to previous versions +- Supports audit trails and compliance requirements + +**Lifecycle Hooks Triggered:** +- `beforeCreate` +- `afterCreate` +- `emitCreatedEvent` + +--- + +### PUT - Editing Existing Snapshots (In-Place Modification) + +**Endpoint:** `PUT /api/{type}/{id}/modified/{modified}` + +**Behavior:** +- **Updates an existing Mongoose document** in-place +- Targets a specific version by `stix.id` AND `stix.modified` +- Uses `_.merge()` to apply changes to the document +- Increments Mongoose `__v` field (optimistic locking counter) +- No new document created - modifies the snapshot directly + +**Example:** +```javascript +// Update the 2024-01-01 version in-place +PUT /api/data-components/x-mitre-data-component--123/modified/2024-01-01T00:00:00.000Z +{ + stix: { + description: "Updated description" + } +} +``` + +**Result:** The existing document is modified: +- Same `_id` in MongoDB +- Same `stix.modified` timestamp +- `__v` incremented from 0 to 1 +- Content updated via `_.merge(document, data)` + +**Use Case:** +- **Rarely used** in practice +- Useful for fixing typos in historical snapshots +- Administrative corrections without creating new versions + +**Lifecycle Hooks Triggered:** +- `beforeUpdate` +- `afterUpdate` +- `emitUpdatedEvent` + +**Important Note on `_.merge()` Behavior:** +- Lodash `_.merge()` performs a **deep merge** +- Properties present in the target but **omitted** from the source are **NOT deleted** +- To remove a property, you must **explicitly set it to `null`** + +```javascript +// This does NOT remove x_mitre_data_source_ref: +PUT /api/data-components/{id}/modified/{modified} +{ + stix: { + name: "New Name" + // x_mitre_data_source_ref omitted + } +} + +// This DOES remove x_mitre_data_source_ref: +PUT /api/data-components/{id}/modified/{modified} +{ + stix: { + name: "New Name", + x_mitre_data_source_ref: null // Explicitly set to null + } +} +``` + +--- + +## Querying Versions + +### Get Latest Version +```javascript +GET /api/data-components/{id}?versions=latest +// Returns the single latest version +``` + +### Get All Versions +```javascript +GET /api/data-components/{id}?versions=all +// Returns array of all versions, ordered by modified date +``` + +### Get Specific Version +```javascript +GET /api/data-components/{id}/modified/{modified} +// Returns a specific snapshot +``` + +--- + +## Embedded Relationships and Versioning + +### The Design Choice + +Embedded relationships are stored **directly on the STIX documents** under `workspace.embedded_relationships`: + +```javascript +{ + stix: { /* STIX properties */ }, + workspace: { + attack_id: "DS0029", + embedded_relationships: [ + { + stix_id: "x-mitre-data-source--abc", + attack_id: "DS0001", // Immutable, safe to denormalize + direction: "outbound" + // Note: 'name' is NOT stored - fetched on read if needed + }, + { + stix_id: "x-mitre-analytic--def", + attack_id: "DA-0001", // Immutable, safe to denormalize + direction: "inbound" + // Note: 'name' is NOT stored - fetched on read if needed + } + ] + } +} +``` + +**What's Stored:** +- ✅ `stix_id` (required) - Reference to the related object +- ✅ `attack_id` (optional) - Immutable, server-generated identifier +- ✅ `direction` (required) - 'inbound' or 'outbound' +- ❌ `name` - NOT stored (mutable, must be fetched on read) + +**Why Not Store Names:** +- Names are **mutable** - users can change them via PUT/POST operations +- Storing them would create **data staleness** issues +- Would require **event propagation** to keep in sync across all references +- MongoDB warns against **unbounded arrays** with duplicated mutable data + +**Benefits:** +- ✅ **Minimal storage** - Only essential relationship data +- ✅ **Always current data** - Names fetched fresh when needed +- ✅ **No sync complexity** - No events needed for name changes +- ✅ **Smaller documents** - Reduces risk of hitting 16MB BSON limit +- ✅ **attack_id denormalization** - Safe because it's immutable + +**Trade-offs:** +- ⚠️ **Additional queries for names** - Must fetch referenced docs when names are needed +- ❌ **No relationship versioning** - Only latest version tracked +- ❌ **Temporal inconsistency** - Old snapshots may have "broken" references + +### Special Case: Fetching Names On-Demand + +When names are needed for embedded relationships (e.g., for display in the frontend), services implement on-demand name lookup: + +**Example: AnalyticsService with `includeEmbeddedRelationships=true`** + +```javascript +async populateEmbeddedRelationshipNames(analytics) { + const detectionStrategiesRepository = require('../../repository/detection-strategies-repository'); + + for (const analytic of analytics) { + if (!analytic.workspace?.embedded_relationships) continue; + + for (const rel of analytic.workspace.embedded_relationships) { + if (rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--')) { + try { + const detectionStrategy = await detectionStrategiesRepository.retrieveLatestByStixId(rel.stix_id); + if (detectionStrategy) { + rel.name = detectionStrategy.stix.name; // Transient property, not persisted + } else { + rel.name = null; + } + } catch (error) { + logger.error(`AnalyticsService: Error fetching detection strategy ${rel.stix_id}:`, error); + rel.name = null; + } + } + } + } +} +``` + +**Key Points:** +- Names are added as **transient properties** to the embedded relationship objects +- Names are **never saved** to the database +- Names are **always fresh** - fetched from the latest version of the referenced object +- This pattern is used sparingly, only when the frontend explicitly needs names + +--- + +## The Temporal Consistency Problem + +### The Scenario + +Consider this sequence of operations: + +1. **POST** a new data source: `DS1` + - Creates: 1 document for DS1 + - `DS1.workspace.embedded_relationships = []` + +2. **POST** a new data component: `DC1` (version 1) + - Creates: 1 document for DC1 v1 + - `DC1.workspace.embedded_relationships = []` + +3. **POST** an update for `DC1` that references `DS1` (version 2) + - Creates: NEW document for DC1 v2 + - Modifies: Existing DS1 document (in-place) + - Result: + - DC1 v1: `embedded_relationships = []` (unchanged) + - DC1 v2: `embedded_relationships = [outbound → DS1]` (new) + - DS1: `embedded_relationships = [inbound ← DC1]` (modified, `__v` = 1) + +4. **POST** another update for `DC1` that removes the reference (version 3) + - Creates: NEW document for DC1 v3 + - Modifies: Existing DS1 document (in-place) + - Result: + - DC1 v1: `embedded_relationships = []` (unchanged) + - DC1 v2: `embedded_relationships = [outbound → DS1]` (unchanged) + - DC1 v3: `embedded_relationships = []` (new) + - DS1: `embedded_relationships = []` (modified, `__v` = 2) + +### The Temporal Mismatch + +After step 4: +- **DC1 v2 (historical snapshot)** says: "I reference DS1" +- **DS1 (current state)** says: "No data components reference me" + +This is a **temporal coupling problem**: +- Data components are **versioned** (immutable snapshots) +- Data sources are **non-versioned** for relationships (mutable state) + +--- + +## Is This a Problem? + +### Short Answer: **Acceptable for Most Use Cases** + +The design prioritizes **operational performance** over **historical queryability**, which is the right trade-off for a knowledge base editing tool where users primarily work with current state. + +### When It's NOT a Problem (99% of Use Cases) + +#### 1. Latest Version Queries +```javascript +// User queries: "Give me DC1" +GET /api/data-components/x-mitre-data-component--123 + +// Returns: Latest snapshot (v3) +// embedded_relationships = [] ✅ Correct + +// User queries: "Give me DS1" +GET /api/data-sources/x-mitre-data-source--456 + +// Returns: Current state +// embedded_relationships = [] ✅ Correct +``` +**Both are in harmony** ✅ + +#### 2. Historical Queries for Single Object +```javascript +// "What did DC1 look like on 2024-01-15?" +GET /api/data-components/x-mitre-data-component--123/modified/2024-01-15T... + +// Returns: Snapshot from that date +// Shows DC1 referenced DS1, which is factually correct ✅ +``` + +#### 3. Audit Trail / Change History +```javascript +// "When did DC1 start referencing DS1?" +GET /api/data-components/x-mitre-data-component--123?versions=all + +// Can determine exactly when x_mitre_data_source_ref was added ✅ +``` + +### When It IS a Problem (Rare Cases) + +#### 1. Historical "Point-in-Time" Queries Across Objects +```javascript +// "Show me DS1 as it existed on 2024-01-15, +// including which data components referenced it" +``` + +**Problem:** DS1 doesn't have relationship snapshots, so you can't reconstruct its `embedded_relationships` at that point in time ❌ + +**Workaround:** Query all DC1 snapshots from that date and reconstruct (expensive) + +#### 2. Bidirectional Navigation from Old Snapshots + +**Scenario:** +- User views DC1 v2 (historical snapshot from 2024-01-15) +- User clicks "Show parent data source" +- DS1 loads with current state + +**Problem:** +- DC1 v2 says: "I reference DS1" +- DS1 (current) says: "No data components reference me" +- Confusing user experience ❌ + +**Mitigation:** UI should clearly indicate "viewing historical snapshot" + +#### 3. Rollback Scenarios + +**Scenario:** User wants to "roll back" to DC1 v2 + +**Problem:** +- DC1 gets restored +- DS1's `embedded_relationships` are out of sync +- Would need to emit events to rebuild DS1's relationships ❌ + +**Mitigation:** Rollback operations would need to trigger relationship recalculation + +--- + +## Why This Design Was Chosen + +### Performance Requirements + +The ATT&CK knowledge base contains: +- ~700 techniques +- ~200 groups +- ~700 software +- ~30 data sources +- ~100+ data components +- Thousands of relationships + +**If relationships were in a separate collection:** +- Every "get data source with components" query requires a join +- Displaying the knowledge base matrix becomes expensive +- API response times degrade + +**With embedded relationships:** +- Single document lookup +- Immutable metadata (attack_id) is denormalized for fast access +- Mutable data (names) fetched on-demand when needed +- Fast reads for the common case (latest version) + +### Acceptable Trade-offs + +1. **Historical relationship queries are rare** + - Users primarily work with latest versions + - Historical analysis is edge case, can be expensive + +2. **Relationship history is preserved in DC snapshots** + - Can reconstruct if needed, just slower + - The data isn't lost, just requires more work to query + +3. **Temporal inconsistency is documented** + - UI can indicate historical snapshot viewing + - Users understand they're looking at a point-in-time view + +4. **No event sourcing requirements** + - System doesn't rely on event replay to rebuild state + - Events are for coordination, not source of truth + +--- + +## Implementation Details + +### How Events Handle Versioning + +When a new version of DC1 is created via POST: + +**In `DataComponentsService.beforeCreate()`:** +```javascript +// Check if this is a new version +let previousVersion = null; +if (data.stix?.id) { + try { + previousVersion = await dataComponentsRepository.retrieveLatestByStixId(data.stix.id); + } catch { + // First version, no previous + } +} + +// Compare old vs new +const oldDataSourceRef = previousVersion?.stix?.x_mitre_data_source_ref; +const newDataSourceRef = data.stix?.x_mitre_data_source_ref; + +// Detect changes +if (oldDataSourceRef && !newDataSourceRef) { + this._removedDataSourceRef = oldDataSourceRef; // Reference removed +} +``` + +**In `DataComponentsService.afterCreate()`:** +```javascript +// Emit removed event for old reference +if (this._removedDataSourceRef) { + await EventBus.emit('x-mitre-data-component::data-source-removed', { + dataComponentId: createdDocument.stix.id, + dataSourceId: this._removedDataSourceRef + }); +} +``` + +**In `DataSourcesService` event listener:** +```javascript +static async handleDataSourceRemoved(payload) { + const { dataComponentId, dataSourceId } = payload; + + // Update DS1 (in-place) + const dataSource = await dataSourcesRepository.retrieveLatestByStixId(dataSourceId); + + // Remove inbound relationship + dataSource.workspace.embedded_relationships = + dataSource.workspace.embedded_relationships.filter( + rel => !(rel.stix_id === dataComponentId && rel.direction === 'inbound') + ); + + await dataSourcesRepository.saveDocument(dataSource); +} +``` + +This ensures that: +- DC1 v3 has correct `embedded_relationships = []` +- DS1 has correct `embedded_relationships = []` +- Both latest versions are in harmony ✅ + +--- + +## Potential Improvements (If Needed) + +### Option 1: Accept the Limitation (Recommended) + +**Actions:** +1. Document this behavior clearly (this document!) +2. Add UI indicators when viewing historical snapshots +3. Provide a "reconstruct relationships at point-in-time" utility if needed + +**Pros:** +- No code changes needed +- Keeps performance benefits +- Works for 99% of use cases + +**Cons:** +- Historical bidirectional queries are expensive + +### Option 2: Snapshot Embedded Relationships Separately + +Create a separate collection to track relationship history: + +```javascript +// New collection: embedded_relationships_history +{ + source_id: "x-mitre-data-component--123", + source_modified: "2024-01-15T...", + target_id: "x-mitre-data-source--456", + direction: "outbound", + created_at: "2024-01-15T...", + deleted_at: null // or timestamp when removed +} +``` + +**Pros:** +- Full historical tracking +- Can reconstruct any point-in-time state +- Bidirectional queries at any date + +**Cons:** +- More storage +- More complex queries +- Adds another collection to maintain + +### Option 3: Version Data Sources Too + +Make data sources fully versioned like data components. + +**Pros:** +- Perfect temporal consistency +- Every snapshot has matching embedded relationships + +**Cons:** +- MUCH more storage (DS1 duplicated on every DC1 change) +- More complex queries (need to find "right" version) +- Breaking change to existing architecture + +### Option 4: Hybrid - Snapshot Only on Relationship Changes + +Only create DS1 snapshots when its `embedded_relationships` actually change. + +**Pros:** +- Less storage than full versioning +- Preserves relationship history + +**Cons:** +- Still adds complexity +- Harder to implement correctly + +--- + +## Best Practices + +### For API Consumers + +1. **Use POST for updates** (default in Workbench frontend) + - Creates proper version history + - Enables rollback + - Triggers correct lifecycle hooks + +2. **Use PUT sparingly** + - Only for administrative corrections + - Be aware of `_.merge()` behavior + - Explicitly set fields to `null` to remove them + +3. **Query latest versions by default** + - `GET /api/data-components/{id}?versions=latest` + - This is what users see in the UI + +4. **Indicate historical snapshots in UI** + - Show banner: "Viewing historical version from 2024-01-15" + - Warn that relationships reflect current state, not historical + +### For Service Developers + +1. **Implement both lifecycle hooks** + - `beforeCreate` / `afterCreate` for POST operations (versioning) + - `beforeUpdate` / `afterUpdate` for PUT operations (in-place edits) + +2. **Detect version changes in `beforeCreate`** + - Fetch previous latest version + - Compare old vs new values + - Store change tracking in instance variables + +3. **Emit events for both added and removed relationships** + - Don't assume POST only adds relationships + - New versions can remove relationships too + +4. **Handle "no previous version" case** + - First version creation is valid + - Emit "referenced" events for initial state + +5. **Never store mutable data in embedded_relationships** + - Only store: `stix_id` (required), `attack_id` (immutable), `direction` (required) + - Never store: `name` or other mutable properties + - Implement on-demand name lookup only if the frontend requires it + - Use transient properties that are never persisted to the database + +--- + +## Summary + +**The current design is acceptable** because: + +✅ ATT&CK Workbench primarily operates on "latest versions" +✅ Historical snapshots show accurate outbound relationships +✅ Historical state can be reconstructed if needed (just expensive) +✅ Performance benefits outweigh historical query complexity +✅ True point-in-time consistency is rarely needed + +The design prioritizes **operational performance** over **historical queryability**, which is the right trade-off for a knowledge base editing tool. + +If you find yourself frequently needing complete historical relationship graphs, consider implementing Option 2 (separate relationship history collection). But for now, this is a **reasonable and well-documented design choice**. + +--- + +## Related Documentation + +- [EVENT_BUS_ARCHITECTURE.md](EVENT_BUS_ARCHITECTURE.md) - Event-driven architecture patterns +- [LIFECYCLE_HOOKS_GUIDE.md](LIFECYCLE_HOOKS_GUIDE.md) - Service lifecycle hooks +- [STIX 2.1 Specification](https://docs.oasis-open.org/cti/stix/v2.1/stix-v2.1.html) - Official STIX standard From 3532b639de0ea53304b66a66a16a3d8c71137f41 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:22:06 -0500 Subject: [PATCH 125/370] fix(collections): convert mongoose documents to plain objects for consistent API responses When retrieveById is called with versions=latest, it uses retrieveLatestByStixId which returns mongoose documents. When adding transient properties like 'contents', these properties are stripped during JSON serialization because they're not part of the schema. This fix ensures consistent behavior between versions=all (which uses .lean() and returns plain objects) and versions=latest (which now converts mongoose documents to plain objects using .toObject()). This allows transient properties to be preserved during JSON serialization. Also fixes retrieveContents query parameter parsing in collections controller to properly handle string true from query strings. Fixes collections API tests where collection.contents was undefined when retrieveContents=true flag was passed. --- app/services/stix/collections-service.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/services/stix/collections-service.js b/app/services/stix/collections-service.js index b0d17f57..370bfe0d 100644 --- a/app/services/stix/collections-service.js +++ b/app/services/stix/collections-service.js @@ -19,6 +19,11 @@ class CollectionsService extends BaseService { * @returns {Promise>} Array of attack objects in the same order as objectList */ async getContents(xMitreContents) { + // Handle empty or undefined contents + if (!xMitreContents || xMitreContents.length === 0) { + return []; + } + const objects = await attackObjectsService.getBulkByIdAndModified(xMitreContents); // Create lookup map for ordering @@ -48,10 +53,15 @@ class CollectionsService extends BaseService { const collection = await this.repository.retrieveLatestByStixId(stixId); if (collection) { + // Convert mongoose document to plain object for consistency with versions=all + // which uses .lean() and returns plain objects + const collectionObj = collection.toObject ? collection.toObject() : collection; + if (options.retrieveContents) { - collection.contents = await this.getContents(collection.stix.x_mitre_contents); + collectionObj.contents = await this.getContents(collectionObj.stix.x_mitre_contents); } - collections = [collection]; + + collections = [collectionObj]; } else { collections = []; } @@ -91,6 +101,11 @@ class CollectionsService extends BaseService { * @yields {Object} Attack objects in order with position metadata */ async *streamContents(xMitreContents) { + // Handle empty or undefined contents + if (!xMitreContents || xMitreContents.length === 0) { + return; + } + // Create a map to track original positions const positionMap = new Map(); xMitreContents.forEach((ref, index) => { From 3de85c56d87ff3db7a2323d7136f690136a383e0 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:22:31 -0500 Subject: [PATCH 126/370] refactor(collections): remove debug console statements --- app/controllers/collections-controller.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/controllers/collections-controller.js b/app/controllers/collections-controller.js index bf9056a3..538a51a1 100644 --- a/app/controllers/collections-controller.js +++ b/app/controllers/collections-controller.js @@ -68,11 +68,6 @@ exports.retrieveVersionById = async function (req, res, next) { const { stixId, modified } = req.params; const { retrieveContents, stream } = req.query; - // Debug logging - console.log('[CONTROLLER] retrieveVersionById called'); - console.log('[CONTROLLER] stream param:', stream, typeof stream); - console.log('[CONTROLLER] retrieveContents param:', retrieveContents, typeof retrieveContents); - const options = { retrieveContents: retrieveContents === true || retrieveContents === 'true', }; @@ -80,12 +75,10 @@ exports.retrieveVersionById = async function (req, res, next) { // Use streaming if requested and contents are being retrieved // Fix: Check for string 'true' since query params are strings if ((stream === true || stream === 'true') && options.retrieveContents) { - console.log('[CONTROLLER] Delegating to streamVersionById'); return exports.streamVersionById(req, res, next); } // Otherwise use regular response - console.log('[CONTROLLER] Using regular response'); const collection = await collectionsService.retrieveVersionById(stixId, modified, options); if (!collection) { From 9e62f659639d3897efd0d3bfbe83a166f93acf4f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:24:41 -0500 Subject: [PATCH 127/370] docs: move markdown docs to docs folder --- .../CROSS_SERVICE_READS_PATTERN.md | 0 EVENT_BUS_ARCHITECTURE.md => docs/EVENT_BUS_ARCHITECTURE.md | 0 IMPLEMENTATION_APPROACH.md => docs/IMPLEMENTATION_APPROACH.md | 0 LIFECYCLE_HOOKS_GUIDE.md => docs/LIFECYCLE_HOOKS_GUIDE.md | 0 .../SERVICE_EXCEPTION_MIDDLEWARE.md | 0 .../STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md | 0 TASK_SCHEDULER.md => docs/TASK_SCHEDULER.md | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename CROSS_SERVICE_READS_PATTERN.md => docs/CROSS_SERVICE_READS_PATTERN.md (100%) rename EVENT_BUS_ARCHITECTURE.md => docs/EVENT_BUS_ARCHITECTURE.md (100%) rename IMPLEMENTATION_APPROACH.md => docs/IMPLEMENTATION_APPROACH.md (100%) rename LIFECYCLE_HOOKS_GUIDE.md => docs/LIFECYCLE_HOOKS_GUIDE.md (100%) rename SERVICE_EXCEPTION_MIDDLEWARE.md => docs/SERVICE_EXCEPTION_MIDDLEWARE.md (100%) rename STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md => docs/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md (100%) rename TASK_SCHEDULER.md => docs/TASK_SCHEDULER.md (100%) diff --git a/CROSS_SERVICE_READS_PATTERN.md b/docs/CROSS_SERVICE_READS_PATTERN.md similarity index 100% rename from CROSS_SERVICE_READS_PATTERN.md rename to docs/CROSS_SERVICE_READS_PATTERN.md diff --git a/EVENT_BUS_ARCHITECTURE.md b/docs/EVENT_BUS_ARCHITECTURE.md similarity index 100% rename from EVENT_BUS_ARCHITECTURE.md rename to docs/EVENT_BUS_ARCHITECTURE.md diff --git a/IMPLEMENTATION_APPROACH.md b/docs/IMPLEMENTATION_APPROACH.md similarity index 100% rename from IMPLEMENTATION_APPROACH.md rename to docs/IMPLEMENTATION_APPROACH.md diff --git a/LIFECYCLE_HOOKS_GUIDE.md b/docs/LIFECYCLE_HOOKS_GUIDE.md similarity index 100% rename from LIFECYCLE_HOOKS_GUIDE.md rename to docs/LIFECYCLE_HOOKS_GUIDE.md diff --git a/SERVICE_EXCEPTION_MIDDLEWARE.md b/docs/SERVICE_EXCEPTION_MIDDLEWARE.md similarity index 100% rename from SERVICE_EXCEPTION_MIDDLEWARE.md rename to docs/SERVICE_EXCEPTION_MIDDLEWARE.md diff --git a/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md b/docs/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md similarity index 100% rename from STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md rename to docs/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md diff --git a/TASK_SCHEDULER.md b/docs/TASK_SCHEDULER.md similarity index 100% rename from TASK_SCHEDULER.md rename to docs/TASK_SCHEDULER.md From 7b3db6d9dddfc142192f03b3cb17b9d952412921 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 21 Nov 2025 08:22:51 -0500 Subject: [PATCH 128/370] fix(embedded-relationships): ensure stale relationships are cleared during versioning When creating a new version of a detection strategy via cloneForCreate(), stale embedded_relationships from the previous version were being carried over, causing incorrect relationship data to persist. Changes: - detection-strategies-service: Unconditionally reset embedded_relationships array in beforeCreate() before rebuilding from x_mitre_analytic_refs - analytics-service: Rebuild external_references array instead of using delete operator on nested Mongoose subdocument properties to ensure URL removal persists correctly The delete operator on Mongoose nested properties doesn't reliably trigger change detection. Rebuilding the array ensures proper persistence when removing URLs from analytics that are no longer referenced by detection strategies. --- app/services/stix/analytics-service.js | 32 +- .../stix/detection-strategies-service.js | 80 +++- app/tests/lib/embedded-relationships.spec.js | 442 ++++++++++++++++++ 3 files changed, 523 insertions(+), 31 deletions(-) create mode 100644 app/tests/lib/embedded-relationships.spec.js diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index c3dc3f5b..0caf7f4f 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -173,26 +173,18 @@ class AnalyticsService extends BaseService { // Update external_references (remove URL since no parent) if (analytic.stix?.external_references) { - // analytic.stix.external_references = removeAttackExternalReferences( - // analytic.stix.external_references, - // ); - - // Rebuild external reference without URL (no parent detection strategy) - const existingAttackRef = - analytic.stix.external_references.find( - (ref) => ref && ref.source_name === 'mitre-attack', - ) || null; - - if (existingAttackRef) delete existingAttackRef.url; - - // const attackRef = { - // source_name: 'mitre-attack', - // external_id: analytic.workspace.attack_id, - // }; - // const attackRef = createAttackExternalReference(analytic.toObject()); - // if (attackRef) { - // analytic.stix.external_references.unshift(attackRef); - // } + // Remove existing ATT&CK external references + analytic.stix.external_references = removeAttackExternalReferences( + analytic.stix.external_references, + ); + + // Rebuild ATT&CK external reference without URL (no parent detection strategy) + const attackRef = { + source_name: 'mitre-attack', + external_id: analytic.workspace.attack_id, + }; + + analytic.stix.external_references.unshift(attackRef); logger.info( `AnalyticsService: Removed external_references URL for analytic ${analyticId}`, diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index 13e94c4d..385ea72b 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -25,21 +25,43 @@ class DetectionStrategiesService extends BaseService { /** * Prepare detection strategy data before creation * Build outbound embedded_relationships for x_mitre_analytic_refs + * Detects if this is a new version and tracks removed relationships */ async beforeCreate(data) { - // Initialize embedded_relationships if not present + // Initialize workspace if not present if (!data.workspace) { data.workspace = {}; } - if (!data.workspace.embedded_relationships) { - data.workspace.embedded_relationships = []; + + // Check if this is a new version of an existing detection strategy + // (same stix.id, but creating a new version with different modified date) + let previousVersion = null; + if (data.stix?.id) { + try { + previousVersion = await detectionStrategiesRepository.retrieveLatestByStixId(data.stix.id); + } catch { + // It's okay if there's no previous version - this might be the first version + logger.debug(`No previous version found for detection strategy ${data.stix.id}`); + } } // Build outbound embedded_relationships for x_mitre_analytic_refs // Cross-repository READS are allowed for denormalization (see CROSS_SERVICE_READS_PATTERN.md) // We emit events in afterCreate/afterUpdate for cross-service WRITES - const analyticRefs = data.stix?.x_mitre_analytic_refs || []; - for (const analyticId of analyticRefs) { + const newAnalyticRefs = data.stix?.x_mitre_analytic_refs || []; + const oldAnalyticRefs = previousVersion?.stix?.x_mitre_analytic_refs || []; + + // Detect changes for event emission + if (previousVersion) { + this._addedAnalyticRefs = newAnalyticRefs.filter((ref) => !oldAnalyticRefs.includes(ref)); + this._removedAnalyticRefs = oldAnalyticRefs.filter((ref) => !newAnalyticRefs.includes(ref)); + } + + // Reset embedded_relationships and rebuild from current x_mitre_analytic_refs + // This ensures stale relationships from previous versions are not carried over + data.workspace.embedded_relationships = []; + + for (const analyticId of newAnalyticRefs) { const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); if (!analytic) { @@ -60,23 +82,59 @@ class DetectionStrategiesService extends BaseService { /** * Handle post-creation logic - * Emit domain event to notify AnalyticsService that analytics were referenced + * Emit domain events to notify AnalyticsService about referenced/removed analytics + * This handles both first-time creation and new version creation (versioning) */ async afterCreate(document) { - const analyticRefs = document.stix?.x_mitre_analytic_refs || []; + const addedRefs = this._addedAnalyticRefs || []; + const removedRefs = this._removedAnalyticRefs || []; - if (analyticRefs.length > 0) { + // Emit event for newly referenced analytics + if (addedRefs.length > 0) { logger.info( - `DetectionStrategiesService: Emitting analytics-referenced event for ${analyticRefs.length} analytic(s)`, - { stixId: document.stix.id, analyticIds: analyticRefs }, + `DetectionStrategiesService: Emitting analytics-referenced event for ${addedRefs.length} added analytic(s)`, + { stixId: document.stix.id, analyticIds: addedRefs }, ); await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { detectionStrategyId: document.stix.id, detectionStrategy: document.toObject ? document.toObject() : document, - analyticIds: analyticRefs, + analyticIds: addedRefs, }); } + + // Emit event for removed analytics (when creating a new version without the analytics) + if (removedRefs.length > 0) { + logger.info( + `DetectionStrategiesService: Emitting analytics-removed event for ${removedRefs.length} removed analytic(s)`, + { stixId: document.stix.id, analyticIds: removedRefs }, + ); + + await EventBus.emit('x-mitre-detection-strategy::analytics-removed', { + detectionStrategyId: document.stix.id, + analyticIds: removedRefs, + }); + } + + // If no changes detected but there are current analytics, emit referenced event + // (this handles the case where this is the first version being created) + const currentAnalyticRefs = document.stix?.x_mitre_analytic_refs || []; + if (!addedRefs.length && !removedRefs.length && currentAnalyticRefs.length > 0) { + logger.info( + `DetectionStrategiesService: Emitting analytics-referenced event for ${currentAnalyticRefs.length} analytic(s)`, + { stixId: document.stix.id, analyticIds: currentAnalyticRefs }, + ); + + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategyId: document.stix.id, + detectionStrategy: document.toObject ? document.toObject() : document, + analyticIds: currentAnalyticRefs, + }); + } + + // Clean up instance variables + delete this._addedAnalyticRefs; + delete this._removedAnalyticRefs; } /** diff --git a/app/tests/lib/embedded-relationships.spec.js b/app/tests/lib/embedded-relationships.spec.js new file mode 100644 index 00000000..7169bfcd --- /dev/null +++ b/app/tests/lib/embedded-relationships.spec.js @@ -0,0 +1,442 @@ +const request = require('supertest'); +const { expect } = require('expect'); + +const database = require('../../lib/database-in-memory'); +const databaseConfiguration = require('../../lib/database-configuration'); +const login = require('../shared/login'); + +const logger = require('../../lib/logger'); +const { cloneForCreate } = require('../shared/clone-for-create'); +logger.level = 'debug'; + +describe('Embedded Relationships - Detection Strategies and Analytics', function () { + let app; + let passportCookie; + + before(async function () { + // Establish the database connection + await database.initializeConnection(); + + // Check for a valid database configuration + await databaseConfiguration.checkSystemConfiguration(); + + // Initialize the express app + app = await require('../../index').initializeApp(); + + // Log into the app + passportCookie = await login.loginAnonymous(app); + }); + + describe('Create Detection Strategy with Analytics', function () { + let analytic1, analytic2, detectionStrategy; + + it('Setup: Create two analytics', async function () { + const timestamp = new Date().toISOString(); + + // Create first analytic + const analytic1Data = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Analytic 1', + type: 'x-mitre-analytic', + spec_version: '2.1', + description: 'Test analytic 1', + created: timestamp, + modified: timestamp, + x_mitre_domains: ['enterprise-attack'], + x_mitre_platforms: ['windows'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res1 = await request(app) + .post('/api/analytics') + .send(analytic1Data) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + analytic1 = res1.body; + expect(analytic1.stix.id).toBeDefined(); + + // Create second analytic + const analytic2Data = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Analytic 2', + type: 'x-mitre-analytic', + spec_version: '2.1', + description: 'Test analytic 2', + created: timestamp, + modified: timestamp, + x_mitre_domains: ['enterprise-attack'], + x_mitre_platforms: ['windows'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res2 = await request(app) + .post('/api/analytics') + .send(analytic2Data) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + analytic2 = res2.body; + expect(analytic2.stix.id).toBeDefined(); + }); + + it('Should create detection strategy with analytics and build outbound embedded_relationships', async function () { + const timestamp = new Date().toISOString(); + + const detectionStrategyData = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Detection Strategy', + type: 'x-mitre-detection-strategy', + spec_version: '2.1', + description: 'Test detection strategy', + created: timestamp, + modified: timestamp, + x_mitre_domains: ['enterprise-attack'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + x_mitre_analytic_refs: [analytic1.stix.id, analytic2.stix.id], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res = await request(app) + .post('/api/detection-strategies') + .send(detectionStrategyData) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + detectionStrategy = res.body; + + // Verify detection strategy was created + expect(detectionStrategy.stix.id).toBeDefined(); + expect(detectionStrategy.stix.x_mitre_analytic_refs).toHaveLength(2); + + // Verify outbound embedded_relationships were created + expect(detectionStrategy.workspace.embedded_relationships).toBeDefined(); + expect(detectionStrategy.workspace.embedded_relationships).toHaveLength(2); + + const outboundRel1 = detectionStrategy.workspace.embedded_relationships.find( + (rel) => rel.stix_id === analytic1.stix.id, + ); + expect(outboundRel1).toBeDefined(); + expect(outboundRel1.direction).toBe('outbound'); + expect(outboundRel1.attack_id).toBe(analytic1.workspace.attack_id); + + const outboundRel2 = detectionStrategy.workspace.embedded_relationships.find( + (rel) => rel.stix_id === analytic2.stix.id, + ); + expect(outboundRel2).toBeDefined(); + expect(outboundRel2.direction).toBe('outbound'); + expect(outboundRel2.attack_id).toBe(analytic2.workspace.attack_id); + }); + + it('Should add inbound embedded_relationships to analytics', async function () { + // Wait a bit for event handlers to complete + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Fetch the updated analytics + const res1 = await request(app) + .get(`/api/analytics/${analytic1.stix.id}`) + .query({ versions: 'latest' }) + .query({ includeRefs: true }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const updatedAnalytic1 = res1.body[0]; + + // Verify inbound embedded_relationship was added + expect(updatedAnalytic1.workspace.embedded_relationships).toBeDefined(); + expect(updatedAnalytic1.workspace.embedded_relationships).toHaveLength(1); + + const inboundRel = updatedAnalytic1.workspace.embedded_relationships[0]; + expect(inboundRel.stix_id).toBe(detectionStrategy.stix.id); + expect(inboundRel.direction).toBe('inbound'); + expect(inboundRel.attack_id).toBe(detectionStrategy.workspace.attack_id); + + // Verify external_references was updated with URL + const attackRef = updatedAnalytic1.stix.external_references.find( + (ref) => ref.source_name === 'mitre-attack', + ); + expect(attackRef).toBeDefined(); + expect(attackRef.url).toBe( + `https://attack.mitre.org/detectionstrategies/${detectionStrategy.workspace.attack_id}#${analytic1.workspace.attack_id}`, + ); + }); + }); + + describe('Update Detection Strategy - Add Analytics', function () { + let analytic3, detectionStrategy2; + + it('Setup: Create an analytic and a detection strategy without analytics', async function () { + // Create analytic + const timestamp1 = new Date().toISOString(); + const analytic3Data = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Analytic 3', + type: 'x-mitre-analytic', + spec_version: '2.1', + description: 'Test analytic 3', + created: timestamp1, + modified: timestamp1, + x_mitre_domains: ['enterprise-attack'], + x_mitre_platforms: ['windows'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res1 = await request(app) + .post('/api/analytics') + .send(analytic3Data) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + analytic3 = res1.body; + + // Create detection strategy without analytics + const timestamp2 = new Date().toISOString(); + const detectionStrategyData = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Detection Strategy 2', + type: 'x-mitre-detection-strategy', + spec_version: '2.1', + description: 'Test detection strategy 2', + created: timestamp2, + modified: timestamp2, + x_mitre_domains: ['enterprise-attack'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + x_mitre_analytic_refs: [], // Empty initially + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res2 = await request(app) + .post('/api/detection-strategies') + .send(detectionStrategyData) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + detectionStrategy2 = res2.body; + + // Verify no embedded_relationships initially + expect(detectionStrategy2.workspace.embedded_relationships?.length || 0).toBe(0); + }); + + it('Should add analytic to detection strategy and create bidirectional relationships', async function () { + const timestamp = new Date().toISOString(); + + // Update detection strategy to add analytic + const updatedData = { + ...detectionStrategy2, + stix: { + ...detectionStrategy2.stix, + modified: timestamp, + x_mitre_analytic_refs: [analytic3.stix.id], + }, + }; + + // Use the actual modified timestamp from the created object + const res = await request(app) + .put( + `/api/detection-strategies/${detectionStrategy2.stix.id}/modified/${detectionStrategy2.stix.modified}`, + ) + .send(updatedData) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + const updatedDetectionStrategy = res.body; + + // Verify outbound relationship was added + expect(updatedDetectionStrategy.workspace.embedded_relationships).toHaveLength(1); + expect(updatedDetectionStrategy.workspace.embedded_relationships[0].stix_id).toBe( + analytic3.stix.id, + ); + expect(updatedDetectionStrategy.workspace.embedded_relationships[0].direction).toBe( + 'outbound', + ); + + // Wait for event handlers + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify inbound relationship was added to analytic + const res2 = await request(app) + .get(`/api/analytics/${analytic3.stix.id}`) + .query({ versions: 'latest', includeRefs: true }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + const updatedAnalytic3 = res2.body[0]; + expect(updatedAnalytic3.workspace.embedded_relationships).toHaveLength(1); + expect(updatedAnalytic3.workspace.embedded_relationships[0].stix_id).toBe( + detectionStrategy2.stix.id, + ); + expect(updatedAnalytic3.workspace.embedded_relationships[0].direction).toBe('inbound'); + + // Verify URL was added + const attackRef = updatedAnalytic3.stix.external_references.find( + (ref) => ref.source_name === 'mitre-attack', + ); + expect(attackRef.url).toBe( + `https://attack.mitre.org/detectionstrategies/${detectionStrategy2.workspace.attack_id}#${analytic3.workspace.attack_id}`, + ); + }); + }); + + describe('Update Detection Strategy - Remove Analytics', function () { + let analytic4, detectionStrategy3; + + it('Setup: Create detection strategy with analytics', async function () { + // Create analytic + const timestamp1 = new Date().toISOString(); + const analytic4Data = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Analytic 4', + type: 'x-mitre-analytic', + spec_version: '2.1', + description: 'Test analytic 4', + created: timestamp1, + modified: timestamp1, + x_mitre_domains: ['enterprise-attack'], + x_mitre_platforms: ['windows'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res1 = await request(app) + .post('/api/analytics') + .send(analytic4Data) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + analytic4 = res1.body; + + // Create detection strategy with analytics + const timestamp2 = new Date().toISOString(); + const detectionStrategyData = { + workspace: { + workflow: { state: 'work-in-progress' }, + }, + stix: { + name: 'Test Detection Strategy 3', + type: 'x-mitre-detection-strategy', + spec_version: '2.1', + description: 'Test detection strategy 3', + created: timestamp2, + modified: timestamp2, + x_mitre_domains: ['enterprise-attack'], + x_mitre_version: '1.0', + x_mitre_attack_spec_version: '3.3.0', + x_mitre_analytic_refs: [analytic4.stix.id], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + }, + }; + + const res2 = await request(app) + .post('/api/detection-strategies') + .send(detectionStrategyData) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + detectionStrategy3 = res2.body; + + // Wait for event handlers + await new Promise((resolve) => setTimeout(resolve, 500)); + }); + + it('Should remove analytic from detection strategy and clean up bidirectional relationships', async function () { + const timestamp = new Date().toISOString(); + + // Update detection strategy to remove analytic + const updatedData = cloneForCreate(detectionStrategy3); + updatedData.stix.modified = timestamp; // Bump timestamp + updatedData.stix.x_mitre_analytic_refs = []; // Remove all analytics + + // Use the actual modified timestamp from the created object + const res = await request(app) + .post('/api/detection-strategies') + .send(updatedData) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201); + + const updatedDetectionStrategy = res.body; + + // Verify outbound relationship was removed + expect(updatedDetectionStrategy.workspace.embedded_relationships?.length || 0).toBe(0); + + // Wait for event handlers + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify inbound relationship was removed from analytic + const res2 = await request(app) + .get(`/api/analytics/${analytic4.stix.id}`) + .query({ versions: 'latest' }) + .query({ includeRefs: true }) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200); + + const updatedAnalytic4 = res2.body[0]; + expect(updatedAnalytic4.workspace.embedded_relationships?.length || 0).toBe(0); + + // Verify URL was removed + const attackRef = updatedAnalytic4.stix.external_references.find( + (ref) => ref.source_name === 'mitre-attack', + ); + expect(attackRef.url).toBeUndefined(); + }); + }); + + after(async function () { + await database.closeConnection(); + }); +}); From 18a3a9f5eecc5934642712ef9f6559fe5a229393 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:33:18 -0500 Subject: [PATCH 129/370] feat(detection-strategies): preserve non-analytic embedded relationships, add uniqueness assertions - Fix beforeCreate to preserve non-analytic embedded_relationships when rebuilding analytic refs Previously, the method would wipe out ALL embedded relationships, not just analytic ones Now filters and preserves non-analytic relationships, matching the pattern used in beforeUpdate - Add assertions module (app/lib/assertions.js) with assertUnique() helper for service-layer validation Assertions check internal invariants and serve as fail-safes when middleware validation is bypassed - Add uniqueness assertion for x_mitre_analytic_refs in beforeCreate and beforeUpdate Consolidated in private _assertAnalyticRefsAreUnique() method Prevents duplicate analytic references from entering the database --- app/lib/assertions.js | 55 +++++++++++++++++++ .../stix/detection-strategies-service.js | 34 ++++++++++-- 2 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 app/lib/assertions.js diff --git a/app/lib/assertions.js b/app/lib/assertions.js new file mode 100644 index 00000000..d366b347 --- /dev/null +++ b/app/lib/assertions.js @@ -0,0 +1,55 @@ +'use strict'; + +const assert = require('assert'); + +/** + * Service-layer assertion utilities + * + * This module provides assertion helpers for validating internal invariants in the service layer. + * Unlike middleware validation (which validates user input), these assertions check for programming + * errors and data integrity issues that should never occur in normal operation. + * + * Usage: + * ``` + * const assertions = require('./lib/assertions'); + * assertions.assertUnique(refs, 'x_mitre_analytic_refs', { stixId: 'x-mitre-detection-strategy--123' }); + * ``` + */ + +/** + * Assert that an array contains only unique values + * + * @param {Array} array - The array to check for uniqueness + * @param {string} fieldName - Name of the field being checked (for error messages) + * @param {object} context - Additional context to include in error message (e.g., { stixId: '...' }) + * @throws {AssertionError} If array contains duplicate values + * + * @example + * assertUnique(['a', 'b', 'c'], 'analytic_refs', { stixId: 'detection-strategy--123' }); + * // Passes + * + * assertUnique(['a', 'b', 'a'], 'analytic_refs', { stixId: 'detection-strategy--123' }); + * // Throws: AssertionError: analytic_refs must contain unique values. Found duplicates in detection-strategy--123 + */ +function assertUnique(array, fieldName, context = {}) { + if (!Array.isArray(array)) { + assert.fail(`${fieldName} must be an array, got ${typeof array}`); + } + + if (array.length === 0) { + return; // Empty arrays are trivially unique + } + + const uniqueValues = new Set(array); + const contextStr = context.stixId ? ` in ${context.stixId}` : ''; + + assert.strictEqual( + uniqueValues.size, + array.length, + `${fieldName} must contain unique values. Found duplicates${contextStr}`, + ); +} + +module.exports = { + assertUnique, +}; diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index 385ea72b..e05d4314 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -7,6 +7,7 @@ const { DetectionStrategy: DetectionStrategyType } = require('../../lib/types'); const logger = require('../../lib/logger'); const EventBus = require('../../lib/event-bus'); const { NotFoundError } = require('../../exceptions'); +const assertions = require('../../lib/assertions'); /** * Service for managing detection strategies @@ -22,12 +23,29 @@ const { NotFoundError } = require('../../exceptions'); * - x-mitre-detection-strategy::analytics-removed */ class DetectionStrategiesService extends BaseService { + /** + * Assertion: Verify x_mitre_analytic_refs contains only unique values + * (This should never actually throw in practice. We have (or will have) validation middleware + * that checks the request body before the service layer runs. That middleware is powered by + * the `@mitre-attack/attack-data-model` library which checks for this condition implicitly. + * This assertion thus serves as a fail-safe in case the middleware is ever somehow bypassed. + * It will throw, causing a 500 exception, but it will block "bad data" from entering the + * database.) + */ + async _assertAnalyticRefsAreUnique(data) { + assertions.assertUnique(data.stix?.x_mitre_analytic_refs, 'x_mitre_analytic_refs', { + stixId: data.stix?.id || 'unknown', + }); + } + /** * Prepare detection strategy data before creation * Build outbound embedded_relationships for x_mitre_analytic_refs * Detects if this is a new version and tracks removed relationships */ async beforeCreate(data) { + this._assertAnalyticRefsAreUnique(data); + // Initialize workspace if not present if (!data.workspace) { data.workspace = {}; @@ -57,10 +75,14 @@ class DetectionStrategiesService extends BaseService { this._removedAnalyticRefs = oldAnalyticRefs.filter((ref) => !newAnalyticRefs.includes(ref)); } - // Reset embedded_relationships and rebuild from current x_mitre_analytic_refs - // This ensures stale relationships from previous versions are not carried over - data.workspace.embedded_relationships = []; + // Preserve non-analytic embedded_relationships and rebuild only analytic refs + // This ensures stale analytic relationships from previous versions are not carried over + // while preserving any other embedded relationships that may exist + const existingNonAnalyticRels = (data.workspace.embedded_relationships || []).filter( + (rel) => !rel.stix_id?.startsWith('x-mitre-analytic--'), + ); + const analyticEmbeddedRels = []; for (const analyticId of newAnalyticRefs) { const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); @@ -72,12 +94,14 @@ class DetectionStrategiesService extends BaseService { }); } - data.workspace.embedded_relationships.push({ + analyticEmbeddedRels.push({ stix_id: analyticId, attack_id: analytic?.workspace?.attack_id || null, direction: 'outbound', }); } + + data.workspace.embedded_relationships = [...existingNonAnalyticRels, ...analyticEmbeddedRels]; } /** @@ -142,6 +166,8 @@ class DetectionStrategiesService extends BaseService { * Detect changes in x_mitre_analytic_refs and update outbound embedded_relationships */ async beforeUpdate(stixId, stixModified, data, existingDocument) { + this._assertAnalyticRefsAreUnique(data); + const oldAnalyticRefs = existingDocument.stix?.x_mitre_analytic_refs || []; const newAnalyticRefs = data.stix?.x_mitre_analytic_refs || []; From b627b792dbd23eb1cb3fc9e49d0541cc3fff8592 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:49:30 -0500 Subject: [PATCH 130/370] build(package-lock): npm i to update package-lock --- package-lock.json | 1027 +-------------------------------------------- 1 file changed, 13 insertions(+), 1014 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2637ad69..6c5553f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -123,977 +123,6 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD", - "optional": true - }, - "node_modules/@aws-sdk/abort-controller": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.303.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-node": "3.303.0", - "@aws-sdk/fetch-http-handler": "3.303.0", - "@aws-sdk/hash-node": "3.303.0", - "@aws-sdk/invalid-dependency": "3.303.0", - "@aws-sdk/middleware-content-length": "3.303.0", - "@aws-sdk/middleware-endpoint": "3.303.0", - "@aws-sdk/middleware-host-header": "3.303.0", - "@aws-sdk/middleware-logger": "3.303.0", - "@aws-sdk/middleware-recursion-detection": "3.303.0", - "@aws-sdk/middleware-retry": "3.303.0", - "@aws-sdk/middleware-sdk-sts": "3.303.0", - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/middleware-user-agent": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/node-http-handler": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/smithy-client": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "@aws-sdk/util-body-length-browser": "3.303.0", - "@aws-sdk/util-body-length-node": "3.303.0", - "@aws-sdk/util-defaults-mode-browser": "3.303.0", - "@aws-sdk/util-defaults-mode-node": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "@aws-sdk/util-user-agent-browser": "3.303.0", - "@aws-sdk/util-user-agent-node": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "fast-xml-parser": "4.1.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/config-resolver": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-config-provider": "3.295.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/credential-provider-ini": "3.303.0", - "@aws-sdk/credential-provider-process": "3.303.0", - "@aws-sdk/credential-provider-sso": "3.303.0", - "@aws-sdk/credential-provider-web-identity": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/token-providers": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/querystring-builder": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-base64": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/hash-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-buffer-from": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/url-parser": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/service-error-classification": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "@aws-sdk/util-retry": "3.303.0", - "tslib": "^2.5.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-retry/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-serde": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/signature-v4": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-middleware": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-endpoints": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-config-provider": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/abort-controller": "3.303.0", - "@aws-sdk/protocol-http": "3.303.0", - "@aws-sdk/querystring-builder": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/property-provider": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/protocol-http": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-builder": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-uri-escape": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/querystring-parser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/service-error-classification": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.303.0", - "@aws-sdk/types": "3.303.0", - "@aws-sdk/util-hex-encoding": "3.295.0", - "@aws-sdk/util-middleware": "3.303.0", - "@aws-sdk/util-uri-escape": "3.303.0", - "@aws-sdk/util-utf8": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/smithy-client": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-stack": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/client-sso-oidc": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/shared-ini-file-loader": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/url-parser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/querystring-parser": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-base64": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-config-provider": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/config-resolver": "3.303.0", - "@aws-sdk/credential-provider-imds": "3.303.0", - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/property-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.295.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-middleware": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-retry": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/service-error-classification": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/types": "3.303.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/node-config-provider": "3.303.0", - "@aws-sdk/types": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.303.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.303.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, "node_modules/@babel/code-frame": { "version": "7.26.2", "dev": true, @@ -2230,7 +1259,6 @@ "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.1.2", @@ -2990,8 +2018,7 @@ }, "node_modules/@types/node": { "version": "14.11.8", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -3076,7 +2103,6 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3148,7 +2174,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3394,11 +2419,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bowser": { - "version": "2.11.0", - "license": "MIT", - "optional": true - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4186,7 +3206,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -4753,7 +3772,6 @@ "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4815,7 +3833,6 @@ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -5190,7 +4207,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5552,21 +4568,6 @@ "version": "3.0.3", "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.1.2", - "license": "MIT", - "optional": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -6680,11 +5681,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ip": { - "version": "2.0.0", - "license": "MIT", - "optional": true - }, "node_modules/ipaddr.js": { "version": "1.9.1", "license": "MIT", @@ -7488,7 +6484,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7963,7 +6958,6 @@ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.3", @@ -11876,7 +10870,6 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11950,6 +10943,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -12291,7 +11290,6 @@ "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -12827,6 +11825,12 @@ "node": ">=12" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13044,11 +12048,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "license": "MIT", - "optional": true - }, "node_modules/super-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", From 162c8f1235a5c3600d412c117b2adf2c772ddb39 Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 3 Dec 2025 14:21:13 -0500 Subject: [PATCH 131/370] chore: add missing variable --- .../api/system-configuration/create-object-identity.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/tests/api/system-configuration/create-object-identity.spec.js b/app/tests/api/system-configuration/create-object-identity.spec.js index d17d4efc..ed06deff 100644 --- a/app/tests/api/system-configuration/create-object-identity.spec.js +++ b/app/tests/api/system-configuration/create-object-identity.spec.js @@ -5,6 +5,7 @@ const uuid = require('uuid'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); const login = require('../../shared/login'); +const config = require('../../../config/config'); const { cloneForCreate } = require('../../shared/clone-for-create'); const logger = require('../../../lib/logger'); From 851d2e4178e1228dfbf8ce76e3fb54cec79b6401 Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 3 Dec 2025 14:30:29 -0500 Subject: [PATCH 132/370] chore: update body-parser version --- package-lock.json | 79 ++++++++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12700eb1..941c57cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@mitre-attack/attack-data-model": "^4.5.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "compression": "^1.8.1", "convict": "^6.2.4", "cors": "^2.8.5", @@ -2357,29 +2357,33 @@ "license": "Apache-2.0" }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/body-parser/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5541,15 +5545,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ignore": { @@ -11036,16 +11044,45 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } diff --git a/package.json b/package.json index 126cadb1..405fd17b 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@mitre-attack/attack-data-model": "^4.5.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "compression": "^1.8.1", "convict": "^6.2.4", "cors": "^2.8.5", From 0f45e7a78311e18d94a6ec7bca133938cd85329a Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 4 Dec 2025 16:20:31 -0500 Subject: [PATCH 133/370] fix: modify tests for adm validation to pass --- app/lib/validation-middleware.js | 2 +- .../adm-validation-middleware.spec.js | 65 ++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 313b3bbd..56d912f9 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -95,7 +95,7 @@ function extractStringLiteralFromStixTypeZodSchema(zodSchema) { function createWorkspaceStixSchema( stixSchema, workflowState, - omitStixFields = ['x_mitre_attack_spec_version'], + omitStixFields = ['x_mitre_attack_spec_version', 'external_references'], ) { logger.debug('Creating combined workspace+STIX schema:', { workflowState, omitStixFields }); diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index cb4b868c..9cc70563 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -29,6 +29,7 @@ const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/d * * NOTE: Tests focus on techniques initially. Once validated, can be generalized to other types. */ + describe('ADM Validation Middleware', function () { let app; let passportCookie; @@ -132,6 +133,48 @@ describe('ADM Validation Middleware', function () { return syntheticStix; } + /** + * Filters the properties of an object, returning a new object containing only + * those entries whose values pass the validity check. + * @param {Object} obj - The object to filter. + * @returns {Object} - A new object with only the valid entries. + */ + function filterObject(obj) { + const out = {}; + for (const [key, value] of Object.entries(obj)) { + const cleaned = clean(value); + if (cleaned !== undefined) out[key] = cleaned; + } + return out; + } + + /** + * Checks if the provided field has a valid value. + * Returns undefined or the value of the field if it is valid + * @param {*} field - The value to validate. + * @returns {value} - Value if the field has a valid value, undefined otherwise. + */ + function clean(value) { + if (value == null) return undefined; // null or undefined + if (typeof value === 'string' && value.trim() === '') return undefined; + if (typeof value === 'number' && Number.isNaN(value)) return undefined; + if (Array.isArray(value)) { + if (value.length === 0) { + return undefined; + } else { + const arr = value.map(v => clean(v)).filter(v => v !== undefined); + return arr.length ? arr : undefined; + } + } + + if (typeof value === 'object') { + const obj = filterObject(value); + return Object.keys(obj).length ? obj : undefined; + } + + return value; + } + before(async function () { // Enable ADM validation and disable OpenAPI validation config.validateRequests.withAttackDataModel = true; @@ -161,6 +204,8 @@ describe('ADM Validation Middleware', function () { const syntheticStix = createSyntheticStix(stixType); const requestBody = { + type: "attack-pattern", + status: "work-in-progress", workspace: { workflow: { state: 'work-in-progress', @@ -333,6 +378,8 @@ describe('ADM Validation Middleware', function () { const syntheticStix = createSyntheticStix(stixType); const createBody = { + type: "attack-pattern", + status: "work-in-progress", workspace: { workflow: { state: 'work-in-progress', @@ -352,7 +399,9 @@ describe('ADM Validation Middleware', function () { }); it('should accept valid updates in work-in-progress state', async function () { - const updateBody = { + let updateBody = { + type: "attack-pattern", + status: "work-in-progress", workspace: { workflow: { state: 'work-in-progress', @@ -367,8 +416,12 @@ describe('ADM Validation Middleware', function () { // Remove server-managed field (server adds this automatically) delete updateBody.stix.x_mitre_attack_spec_version; + // Unset/remove the external_references field because that is handled by the rest-api + delete updateBody.stix.external_references; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + // Filters the properties of an object, returning a new object containing only those entries whose values pass the validity check. + updateBody = filterObject(updateBody); const res = await request(app) .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) @@ -387,7 +440,9 @@ describe('ADM Validation Middleware', function () { }); it('should accept updates with missing optional fields in work-in-progress state', async function () { - const updateBody = { + let updateBody = { + type: "attack-pattern", + status: "work-in-progress", workspace: { workflow: { state: 'work-in-progress', @@ -402,11 +457,15 @@ describe('ADM Validation Middleware', function () { // Remove optional fields to test partial validation delete updateBody.stix.description; delete updateBody.stix.x_mitre_platforms; + // Unset/remove the external_references field because that is handled by the rest-api + delete updateBody.stix.external_references; // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure + // Filters the properties of an object, returning a new object containing only those entries whose values pass the validity check. + updateBody = filterObject(updateBody); const res = await request(app) .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) @@ -487,6 +546,8 @@ describe('ADM Validation Middleware', function () { // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; + // Unset/remove the external_references field because that is handled by the rest-api + delete updateBody.stix.external_references; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure const res = await request(app) From ee5e024ec5fec326217c1edc19a6629642216037 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 4 Dec 2025 16:31:48 -0500 Subject: [PATCH 134/370] chore: remove the correct fields for validation to pass --- .../adm-validation-middleware.spec.js | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 9cc70563..5839ec46 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -162,7 +162,7 @@ describe('ADM Validation Middleware', function () { if (value.length === 0) { return undefined; } else { - const arr = value.map(v => clean(v)).filter(v => v !== undefined); + const arr = value.map((v) => clean(v)).filter((v) => v !== undefined); return arr.length ? arr : undefined; } } @@ -204,8 +204,8 @@ describe('ADM Validation Middleware', function () { const syntheticStix = createSyntheticStix(stixType); const requestBody = { - type: "attack-pattern", - status: "work-in-progress", + type: 'attack-pattern', + status: 'work-in-progress', workspace: { workflow: { state: 'work-in-progress', @@ -214,6 +214,8 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + delete requestBody.stix.external_references; + const res = await request(app) .post(endpoint) .send(requestBody) @@ -244,6 +246,8 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + delete requestBody.stix.external_references; + const res = await request(app) .post(endpoint) .send(requestBody) @@ -290,7 +294,7 @@ describe('ADM Validation Middleware', function () { it('should accept valid complete data in reviewed state', async function () { const syntheticStix = createSyntheticStix(stixType); - const requestBody = { + let requestBody = { workspace: { workflow: { state: 'reviewed', @@ -299,6 +303,13 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + requestBody.stix.kill_chain_phases = [ + { + "kill_chain_name": "mitre-attack", + "phase_name": "initial-access" + } + ] + const res = await request(app) .post(endpoint) .send(requestBody) @@ -377,9 +388,9 @@ describe('ADM Validation Middleware', function () { // Create an object to update const syntheticStix = createSyntheticStix(stixType); - const createBody = { - type: "attack-pattern", - status: "work-in-progress", + let createBody = { + type: 'attack-pattern', + status: 'work-in-progress', workspace: { workflow: { state: 'work-in-progress', @@ -388,6 +399,9 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + delete createBody.stix.external_references; + createBody = filterObject(createBody); + const createRes = await request(app) .post(endpoint) .send(createBody) @@ -400,8 +414,8 @@ describe('ADM Validation Middleware', function () { it('should accept valid updates in work-in-progress state', async function () { let updateBody = { - type: "attack-pattern", - status: "work-in-progress", + type: 'attack-pattern', + status: 'work-in-progress', workspace: { workflow: { state: 'work-in-progress', @@ -441,8 +455,8 @@ describe('ADM Validation Middleware', function () { it('should accept updates with missing optional fields in work-in-progress state', async function () { let updateBody = { - type: "attack-pattern", - status: "work-in-progress", + type: 'attack-pattern', + status: 'work-in-progress', workspace: { workflow: { state: 'work-in-progress', From 84559af7457a0e36d48bc0e078b36f39f547f9b9 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 4 Dec 2025 16:41:21 -0500 Subject: [PATCH 135/370] chore: fix identation issues --- app/tests/middleware/adm-validation-middleware.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 5839ec46..c124b94a 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -303,6 +303,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + requestBody.stix.kill_chain_phases = [ { "kill_chain_name": "mitre-attack", @@ -400,6 +401,8 @@ describe('ADM Validation Middleware', function () { }; delete createBody.stix.external_references; + + // Filters the properties of an object, returning a new object containing only those entries whose values pass the validity check. createBody = filterObject(createBody); const createRes = await request(app) From edfe3c66ef5c64989ae9930f31bfab08f5ebe880 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 4 Dec 2025 16:42:19 -0500 Subject: [PATCH 136/370] chore: fix identation issues --- app/tests/middleware/adm-validation-middleware.spec.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index c124b94a..dfd8abcc 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -303,13 +303,12 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; - requestBody.stix.kill_chain_phases = [ { - "kill_chain_name": "mitre-attack", - "phase_name": "initial-access" - } - ] + kill_chain_name: 'mitre-attack', + phase_name: 'initial-access', + }, + ]; const res = await request(app) .post(endpoint) From 42d8427897958b5315515f3cfc309e9af51c2de4 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 4 Dec 2025 16:59:51 -0500 Subject: [PATCH 137/370] fix: adm validation tests for reviewed objects --- .../adm-validation-middleware.spec.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index dfd8abcc..c27acd91 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -303,6 +303,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + // Need to add kill_chain_phases for validation to pass for reviewed techniques requestBody.stix.kill_chain_phases = [ { kill_chain_name: 'mitre-attack', @@ -310,6 +311,9 @@ describe('ADM Validation Middleware', function () { }, ]; + // Unset/remove the external_references field because that is handled by the rest-api + delete requestBody.stix.external_references; + const res = await request(app) .post(endpoint) .send(requestBody) @@ -528,7 +532,7 @@ describe('ADM Validation Middleware', function () { // Create an object to update const syntheticStix = createSyntheticStix(stixType); - const createBody = { + let createBody = { workspace: { workflow: { state: 'work-in-progress', @@ -537,6 +541,9 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + // Unset/remove the external_references field because that is handled by the rest-api + delete createBody.stix.external_references; + const createRes = await request(app) .post(endpoint) .send(createBody) @@ -548,7 +555,7 @@ describe('ADM Validation Middleware', function () { }); it('should accept valid complete updates in reviewed state', async function () { - const updateBody = { + let updateBody = { workspace: { workflow: { state: 'reviewed', @@ -560,6 +567,14 @@ describe('ADM Validation Middleware', function () { }, }; + // Need to add kill_chain_phases for validation to pass for reviewed techniques + updateBody.stix.kill_chain_phases = [ + { + kill_chain_name: 'mitre-attack', + phase_name: 'initial-access', + } + ] + // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; // Unset/remove the external_references field because that is handled by the rest-api From 388846d24b42f78364d2f5ede4a9faaed410cb19 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 4 Dec 2025 17:00:27 -0500 Subject: [PATCH 138/370] chore: fix identation issues --- app/tests/middleware/adm-validation-middleware.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index c27acd91..a03b6d9d 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -567,13 +567,13 @@ describe('ADM Validation Middleware', function () { }, }; - // Need to add kill_chain_phases for validation to pass for reviewed techniques - updateBody.stix.kill_chain_phases = [ - { + // Need to add kill_chain_phases for validation to pass for reviewed techniques + updateBody.stix.kill_chain_phases = [ + { kill_chain_name: 'mitre-attack', phase_name: 'initial-access', - } - ] + }, + ]; // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; From 370812a4de1521bd9695ad9ae35d80423b0eb8e5 Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 5 Dec 2025 14:55:28 -0600 Subject: [PATCH 139/370] fix: release initial beta version From 038ed16e80191d7aba9dfdf6f2208d8f9eda2da9 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:32:04 -0500 Subject: [PATCH 140/370] build: update package-lock.json --- package-lock.json | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/package-lock.json b/package-lock.json index f301207f..a848c3f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3573,6 +3573,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "dev": true, @@ -5106,6 +5121,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -6164,6 +6194,12 @@ "version": "2.1.3", "license": "MIT" }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", @@ -11453,6 +11489,12 @@ "node": ">=8" } }, + "node_modules/sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", From b9b6aad2c1b56e951ec781d7600452df762daa65 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:50:15 -0500 Subject: [PATCH 141/370] build: update package-lock.json --- package-lock.json | 14622 +++++++++++++++++++++++--------------------- 1 file changed, 7681 insertions(+), 6941 deletions(-) diff --git a/package-lock.json b/package-lock.json index a848c3f7..32f29013 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,9 +107,9 @@ "license": "MIT" }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-13.0.1.tgz", - "integrity": "sha512-91uy6MGWqu7CjcV7tLPMuYh/Wj/RNPBXquSdEaCEpj2H/cFy0Yu+t1EdxExSyaryl1ykhDo30plq9tIm/HVZnw==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-13.0.5.tgz", + "integrity": "sha512-xfh4xVJD62gG6spIc7lwxoWT+l16nZu1ELyU8FkjaP/oD2yP09EvLAU6KhtudN9aML2Khhs9pY6Slr7KGTES3w==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.15", @@ -123,28 +123,43 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -167,159 +182,10 @@ "semver": "^7.3.2" } }, - "node_modules/@codedependant/semantic-release-docker/node_modules/@semantic-release/error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", - "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@colors/colors": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "license": "MIT", "optional": true, "engines": { @@ -348,40 +214,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@commitlint/config-conventional": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", @@ -452,19 +284,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/format/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/is-ignored": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", @@ -517,19 +336,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/message": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", @@ -555,61 +361,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/parse/node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@commitlint/parse/node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@commitlint/parse/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/parse/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/@commitlint/read": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", @@ -645,16 +396,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@commitlint/rules": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", @@ -708,44 +449,53 @@ "node": ">=v18" } }, - "node_modules/@commitlint/types/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@dabh/diagnostics": { - "version": "2.0.2", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", "license": "MIT", "dependencies": { - "colorspace": "1.1.x", + "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -753,13 +503,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -767,45 +517,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -816,9 +544,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -828,7 +556,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -856,24 +584,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -881,17 +591,20 @@ "dev": true, "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@eslint/eslintrc/node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { @@ -902,9 +615,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -912,13 +625,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -927,23 +640,17 @@ }, "node_modules/@ewoudenberg/difflib": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ewoudenberg/difflib/-/difflib-0.1.0.tgz", + "integrity": "sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==", "dev": true, "dependencies": { "heap": ">= 0.2.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -951,31 +658,23 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1000,97 +699,10 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { @@ -1099,6 +711,8 @@ }, "node_modules/@jest/expect-utils": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { @@ -1110,6 +724,8 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { @@ -1121,6 +737,8 @@ }, "node_modules/@jest/types": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "dependencies": { @@ -1135,8 +753,27 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1144,27 +781,33 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.1.tgz", - "integrity": "sha512-0b5Q+67SzsfHjAWYVCL8AQ5qlNa+poLDhfWt4n7vYQKQwyqJr6BEU7x+mcQpj4V/+B0qmJiBWx7uBeb0h778rQ==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.2.tgz", + "integrity": "sha512-4MzEkYcXqe5rWnlj6Iy/aE4+wXkAv3q7rJUOHairl24HtiTqDHxVavz96KJBNNpRyIcEXTlCm93AQpVp/uR4MA==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", @@ -1186,7 +829,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -1204,204 +849,150 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/core": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz", - "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.1.2", - "@octokit/request": "^9.2.1", - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", - "before-after-hook": "^3.0.2", + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/endpoint": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz", - "integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.6.2", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/graphql": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz", - "integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^9.2.2", - "@octokit/types": "^13.8.0", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, "license": "MIT" }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.4.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.3.tgz", - "integrity": "sha512-tBXaAbXkqVJlRoA/zQVe9mUdb8rScmivqtpv3ovsC5xhje/a+NOCivs7eUhWBwCApJVsR4G5HMeaLbq7PxqZGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.7.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, "node_modules/@octokit/plugin-retry": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.4.tgz", - "integrity": "sha512-7AIP4p9TttKN7ctygG4BtR7rrB0anZqoU9ThXFk8nETqIfvgPUANTSYHqWYknK7W3isw59LpZeLI8pcEwiJdRg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", + "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": ">=6" + "@octokit/core": ">=7" } }, "node_modules/@octokit/plugin-throttling": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.4.0.tgz", - "integrity": "sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.7.0", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "^6.1.3" + "@octokit/core": "^7.0.0" } }, "node_modules/@octokit/request": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz", - "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^10.1.3", - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", - "fast-content-type-parse": "^2.0.0", + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/request-error": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz", - "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.6.2" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^27.0.0" } }, "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "license": "MIT", "dependencies": { "@noble/hashes": "^1.1.5" @@ -1409,6 +1000,8 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -1417,9 +1010,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -1476,6 +1069,8 @@ }, "node_modules/@scarf/scarf": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", "hasInstallScript": true, "license": "Apache-2.0" }, @@ -1509,63 +1104,99 @@ "semantic-release": ">=20.1.0" } }, - "node_modules/@semantic-release/commit-analyzer/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-writer": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", + "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" }, "engines": { - "node": ">=6.0" + "node": ">=18" + } + }, + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "meow": "^13.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@semantic-release/commit-analyzer/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@semantic-release/commit-analyzer/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=14.17" } }, "node_modules/@semantic-release/github": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.1.tgz", - "integrity": "sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", + "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^6.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^9.0.0", + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", - "globby": "^14.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", "url-join": "^5.0.0" }, "engines": { @@ -1575,75 +1206,101 @@ "semantic-release": ">=24.1.0" } }, - "node_modules/@semantic-release/github/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/@semantic-release/github/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@semantic-release/github/node_modules/@octokit/plugin-paginate-rest": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", "dev": true, "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.1" + }, "engines": { - "node": ">= 14" + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/@semantic-release/github/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@semantic-release/github/node_modules/@octokit/types": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", + "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, + "@octokit/openapi-types": "^26.0.0" + } + }, + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0" + "node": ">=18" + } + }, + "node_modules/@semantic-release/github/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "supports-color": { + "picomatch": { "optional": true } } }, - "node_modules/@semantic-release/github/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/@semantic-release/github/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, "engines": { - "node": ">= 14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@semantic-release/github/node_modules/mime": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz", - "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==", + "node_modules/@semantic-release/github/node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, - "funding": [ - "https://github.com/sponsors/broofa" - ], "license": "MIT", - "bin": { - "mime": "bin/cli.js" + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=16" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/@semantic-release/github/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@semantic-release/npm": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", - "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", + "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1654,7 +1311,7 @@ "lodash-es": "^4.17.21", "nerf-dart": "^1.0.0", "normalize-url": "^8.0.0", - "npm": "^10.5.0", + "npm": "^10.9.3", "rc": "^1.2.8", "read-pkg": "^9.0.0", "registry-auth-token": "^5.0.0", @@ -1668,106 +1325,102 @@ "semantic-release": ">=20.1.0" } }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=14.14" + "node": ">=18" } }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.3.tgz", - "integrity": "sha512-XxAZRPWGwO5JwJtS83bRdoIhCiYIx8Vhr+u231pQAsdFIAbm19rSVJLdnBN+Avvk7CKvNQE/nJ4y7uqKH6WTiw==", + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "get-stream": "^7.0.0", - "import-from-esm": "^2.0.0", - "into-stream": "^7.0.0", - "lodash-es": "^4.17.21", - "read-package-up": "^11.0.0" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=20.8.1" + "node": "^18.19.0 || >=20.5.0" }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@semantic-release/npm/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=14.14" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@semantic-release/npm/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -1777,2675 +1430,2794 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.1", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.1", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.1", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, "engines": { - "node": ">=4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.1", + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/connect": { - "version": "3.4.35", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/node": "*" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/conventional-commits-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", - "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@types/estree": { - "version": "1.0.6", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/@types/express": { - "version": "5.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/qs": "*", - "@types/serve-static": "*" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "5.0.0", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC" }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "license": "MIT" - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.7", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "license": "MIT" - }, - "node_modules/@types/multer": { - "version": "1.4.12", - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/express": "*" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/node": { - "version": "14.11.8", - "license": "MIT" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", "dev": true, - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.4", - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/serve-static": { - "version": "1.13.10", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", "dev": true, - "license": "MIT" - }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "license": "MIT" - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/webidl-conversions": "*" + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/yargs": { - "version": "17.0.33", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/yargs-parser": "*" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, "bin": { - "acorn": "bin/acorn" + "pacote": "bin/index.js" }, "engines": { - "node": ">=0.4.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "which": "^5.0.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "environment": "^1.0.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ansi-styles": { - "version": "5.2.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, - "license": "MIT" - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } }, - "node_modules/argv-formatter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", - "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", "dev": true, - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.1", "dev": true, - "license": "MIT" - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "license": "MIT" + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/async-await-retry": { - "version": "2.1.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, "license": "MIT", - "bin": { - "async-await-retry": "index.js" - }, "engines": { - "node": ">=7.6.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/async-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/abbrev": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/agent-base": { + "version": "7.1.3", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": ">= 14" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/axios": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", - "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/aproba": { + "version": "2.0.0", "dev": true, - "license": "Apache-2.0" + "inBundle": true, + "license": "ISC" }, - "node_modules/balanced-match": { - "version": "1.0.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/archy": { + "version": "1.0.0", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", "dev": true, - "license": "Apache-2.0", - "optional": true + "inBundle": true, + "license": "MIT" }, - "node_modules/basic-auth": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "safe-buffer": "5.1.2" + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" }, "engines": { - "node": ">= 0.8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", "dev": true, - "license": "Apache-2.0" + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { - "node": ">=6.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "ee-first": "1.1.1" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/chalk": { + "version": "5.4.1", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/braces": { - "version": "3.0.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ci-info": { + "version": "4.2.0", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, "engines": { "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", "dev": true, - "license": "ISC" - }, - "node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", - "license": "Apache-2.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, "engines": { - "node": ">=16.20.1" + "node": ">=14" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": "*" + "node": ">= 10" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=10.16.0" + "node": ">=7.0.0" } }, - "node_modules/bytes": { - "version": "3.1.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 8" } }, - "node_modules/c8": { - "version": "10.1.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "@bcoe/v8-coverage": "^1.0.1", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^7.0.1", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" + "isexe": "^2.0.0" }, "bin": { - "c8": "bin/c8.js" + "node-which": "bin/node-which" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "monocart-coverage-reports": "^2" - }, - "peerDependenciesMeta": { - "monocart-coverage-reports": { - "optional": true - } + "node": ">= 8" } }, - "node_modules/c8/node_modules/cliui": { - "version": "8.0.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/c8/node_modules/find-up": { - "version": "5.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/debug": { + "version": "4.4.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=10" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/c8/node_modules/locate-path": { - "version": "6.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/diff": { + "version": "5.2.0", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, + "inBundle": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.3.1" } }, - "node_modules/c8/node_modules/p-limit": { - "version": "3.1.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/encoding": { + "version": "0.1.13", "dev": true, + "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "iconv-lite": "^0.6.2" } }, - "node_modules/c8/node_modules/p-locate": { - "version": "5.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/c8/node_modules/yargs": { - "version": "17.7.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", "dev": true, + "inBundle": true, "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=12" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 0.4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">= 0.4" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/callsites": { - "version": "3.1.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } + "inBundle": true, + "license": "ISC" }, - "node_modules/camelcase": { - "version": "6.3.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/hosted-git-info": { + "version": "8.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/chalk": { - "version": "4.1.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 14" } }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14" } }, - "node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", "dev": true, + "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { - "color-name": "~1.1.4" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=0.10.0" } }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.4", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.8.19" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ini": { + "version": "5.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ci-info": { - "version": "3.9.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "5.0.0" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 12" } }, - "node_modules/clean-stack/node_modules/escape-string-regexp": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/parse5": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/is-cidr": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "dev": true, - "license": "MIT" - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "string-width": "^4.2.0" + "cidr-regex": "^4.1.1" }, "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "node": ">=14" } }, - "node_modules/cliui": { - "version": "7.0.4", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", + "inBundle": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=8" } }, - "node_modules/color": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" }, - "node_modules/color-convert": { - "version": "1.9.3", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "color-name": "1.1.3" + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/color-name": { - "version": "1.1.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/color-string": { - "version": "1.5.5", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colors": { - "version": "1.4.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/colorspace": { - "version": "1.1.2", - "license": "MIT", - "dependencies": { - "color": "3.0.x", - "text-hex": "1.0.x" + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" }, - "node_modules/commander": { - "version": "9.3.0", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/component-emitter": { - "version": "1.3.0", - "license": "MIT" - }, - "node_modules/compressible": { - "version": "2.0.18", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "@npmcli/arborist": "^8.0.1", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.1", "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" + "@npmcli/arborist": "^8.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" }, "engines": { - "node": ">= 6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "safe-buffer": "5.2.1" + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/content-type": { - "version": "1.0.5", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/conventional-changelog-angular": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmsearch": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "compare-func": "^2.0.0" + "npm-registry-fetch": "^18.0.1" }, "engines": { - "node": ">=18" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", - "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "compare-func": "^2.0.0" + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" }, "engines": { - "node": ">=16" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/conventional-changelog-writer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", - "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "conventional-commits-filter": "^5.0.0", - "handlebars": "^4.7.7", - "meow": "^13.0.0", - "semver": "^7.5.2" - }, - "bin": { - "conventional-changelog-writer": "dist/cli/index.js" + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" }, "engines": { - "node": ">=18" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/conventional-commits-filter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "inBundle": true, + "license": "ISC" }, - "node_modules/conventional-commits-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", - "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" }, "engines": { - "node": ">=18" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.6" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/convert-source-map": { - "version": "1.7.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass": { + "version": "7.1.2", "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "node_modules/convict": { - "version": "6.2.4", - "license": "Apache-2.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "lodash.clonedeep": "^4.5.0", - "yargs-parser": "^20.2.7" + "minipass": "^7.0.3" }, "engines": { - "node": ">=6" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/convict/node_modules/yargs-parser": { - "version": "20.2.9", - "license": "ISC", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/cookie": { - "version": "1.0.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": ">= 8" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz", - "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "jiti": "^2.4.1" + "yallist": "^4.0.0" }, "engines": { - "node": ">=v18" - }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=9", - "typescript": ">=5" + "node": ">=8" } }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "luxon": "^3.2.1" + "minipass": "^3.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minizlib": { + "version": "3.0.2", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "type-fest": "^1.0.1" + "minipass": "^7.1.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", "dev": true, - "license": "(MIT OR CC0-1.0)", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ms": { + "version": "2.1.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "inBundle": true, + "license": "MIT" }, - "node_modules/date-fns": { - "version": "2.28.0", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/debug": { - "version": "2.6.9", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp": { + "version": "11.2.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=4.0.0" + "node": ">=18" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", "dev": true, - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", + "inBundle": true, "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, "engines": { - "node": ">=0.4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/nopt": { + "version": "8.1.0", + "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/diff": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/normalize-package-data": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, "engines": { - "node": ">=0.3.1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "path-type": "^4.0.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "is-obj": "^2.0.0" + "semver": "^7.1.1" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/dreamopt": { - "version": "0.8.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", "dev": true, - "dependencies": { - "wordwrap": ">=0.0.2" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=0.4.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "ISC", "dependencies": { - "readable-stream": "^2.0.2" + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", "dev": true, - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "license": "Apache-2.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "safe-buffer": "^5.0.1" + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", "dev": true, - "license": "MIT" - }, - "node_modules/enabled": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, "engines": { - "node": ">= 0.8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "once": "^1.4.0" + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", "dev": true, + "inBundle": true, "license": "BSD-2-Clause", "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/env-ci": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", - "integrity": "sha512-Z8dnwSDbV1XYM9SBF2J0GcNVvmfmfh3a49qddGIROhBoVro6MZVTji15z/sJbQ2ko2ei8n988EU1wzoLU/tF+g==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/p-map": { + "version": "7.0.3", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "execa": "^8.0.0", - "java-properties": "^1.0.2" - }, "engines": { - "node": "^18.17 || >=20.6.1" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/env-ci/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/pacote": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" }, - "engines": { - "node": ">=16.17" + "bin": { + "pacote": "bin/index.js" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/env-ci/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/env-ci/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/path-key": { + "version": "3.1.1", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "MIT", "engines": { - "node": ">=16.17.0" + "node": ">=8" } }, - "node_modules/env-ci/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/env-ci/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "path-key": "^4.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/env-ci/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/env-ci/node_modules/strip-final-newline": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/proggy": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=12" - }, + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promzard": { + "version": "2.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "is-arrayish": "^0.2.1" + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", "dev": true, - "license": "MIT" + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "es-errors": "^1.3.0" + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 4" } }, - "node_modules/escalade": { - "version": "3.1.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", "dev": true, + "inBundle": true, "license": "MIT", + "optional": true + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": ">=8" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" }, "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz", - "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore": { + "version": "3.1.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { + "version": "3.1.0", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { + "version": "2.0.0", "dev": true, + "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { + "version": "3.1.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { + "version": "2.1.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "ms": "2.1.2" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/socks": { + "version": "2.8.5", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", "dev": true, - "license": "ISC", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.21", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/string-width": { + "version": "4.2.3", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "MIT", "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/esquery": { - "version": "1.5.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "ISC", "dependencies": { - "estraverse": "^5.1.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">=0.10" + "node": ">= 8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "ISC", "dependencies": { - "estraverse": "^5.2.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/estraverse": { - "version": "5.3.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=4.0" + "node": ">=8" } }, - "node_modules/esutils": { - "version": "2.0.3", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/execa": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", - "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.14", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.3", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.0", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.0.0" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": "^18.19.0 || >=20.5.0" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/execa/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", "dev": true, + "inBundle": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/execa/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/expect": { - "version": "29.7.0", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" }, "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/express-openapi-validator": { - "version": "5.5.7", - "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.5.7.tgz", - "integrity": "sha512-QcV941hD3GHnCOg72y6kgqA7XsDkmZOGf7tvoIw9IyUlIRvJv/XDpYFnK7rOggt+ZUaM2P/9wsZCmdMXpr7z2A==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@apidevtools/json-schema-ref-parser": "^12.0.1", - "@types/multer": "^1.4.12", - "ajv": "^8.17.1", - "ajv-draft-04": "^1.0.0", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "json-schema-traverse": "^1.0.0", - "lodash.clonedeep": "^4.5.0", - "lodash.get": "^4.4.2", - "media-typer": "^1.1.0", - "multer": "^2.0.1", - "ono": "^7.1.3", - "path-to-regexp": "^8.2.0", - "qs": "^6.14.0" + "unique-slug": "^5.0.0" }, - "peerDependencies": { - "express": "*" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/express-openapi-validator/node_modules/@apidevtools/json-schema-ref-parser": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-12.0.2.tgz", - "integrity": "sha512-SoZWqQz4YMKdw4kEMfG5w6QAy+rntjsoAT1FtvZAnVEnCR4uy9YSuDBNoVAFHgzSz0dJbISLLCSrGR2Zd7bcvA==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" + "imurmurhash": "^0.1.4" }, "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/express-openapi-validator/node_modules/ajv-draft-04": { - "version": "1.0.0", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/express-openapi-validator/node_modules/ajv-formats": { + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/express-session": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", - "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/which": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.1.0", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">= 0.8.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": ">=16" } }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "license": "MIT" - }, - "node_modules/express-session/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" + "node": ">=12" }, - "engines": { - "node": ">= 0.8" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.6" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=0.6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/express/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/yallist": { + "version": "4.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, "engines": { - "node": ">=8.6.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.3", - "license": "BSD-3-Clause" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "node_modules/@semantic-release/npm/node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fecha": { - "version": "4.2.3", - "license": "MIT" - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "node_modules/@semantic-release/npm/node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=18" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/figures/node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", "engines": { @@ -4455,124 +4227,129 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", + "node_modules/@semantic-release/release-notes-generator": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", + "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^2.0.0", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "to-regex-range": "^5.0.1" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-writer": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", + "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" }, "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, "engines": { - "node": ">=8" + "node": ">=16" }, "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, - "node_modules/find-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", - "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^7.2.0", - "path-exists": "^5.0.0", - "unicorn-magic": "^0.1.0" - }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", "engines": { @@ -4582,1658 +4359,2055 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/find-up/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=4" } }, - "node_modules/find-up/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", + "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@types/node": "*" } }, - "node_modules/find-versions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", - "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "license": "MIT", "dependencies": { - "semver-regex": "^4.0.5", - "super-regex": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, - "node_modules/flat": { - "version": "5.0.2", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/flat-cache": { - "version": "4.0.1", + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/flatted": { - "version": "3.3.2", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "license": "ISC" - }, - "node_modules/fn-args": { - "version": "5.0.0", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@types/istanbul-lib-report": "*" } }, - "node_modules/fn.name": { - "version": "1.1.0", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "dependencies": { + "@types/ms": "*", + "@types/node": "*" } }, - "node_modules/foreground-child": { - "version": "3.3.0", - "dev": true, - "license": "ISC", + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@types/express": "*" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "undici-types": "~7.16.0" } }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "license": "MIT", "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" + "@types/node": "*" } }, - "node_modules/forwarded": { - "version": "0.2.0", + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "peer": true, + "dependencies": { + "@types/webidl-conversions": "*" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "@types/yargs-parser": "*" } }, - "node_modules/fs-extra": { - "version": "10.1.0", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=12" + "node": ">= 0.6" } }, - "node_modules/function-bind": { - "version": "1.1.2", + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.6" } }, - "node_modules/function-timeout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", - "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "bin": { + "acorn": "bin/acorn" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=0.4.0" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, "engines": { - "node": ">= 0.4" + "node": ">= 14" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-log-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", - "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", - "dev": true, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "0.6.8" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/git-raw-commits": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "dev": true, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" + "ajv": "^8.0.0" }, - "bin": { - "git-raw-commits": "cli.mjs" + "peerDependencies": { + "ajv": "^8.0.0" }, - "engines": { - "node": ">=16" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, "engines": { - "node": ">=16.10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/git-raw-commits/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">= 10.x" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/glob": { - "version": "10.4.5", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "color-convert": "^2.0.1" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/glob-parent": { - "version": "5.1.2", + "node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 6" + "node": ">=7.0.0" } }, - "node_modules/glob/node_modules/brace-expansion": { + "node_modules/ansi-styles/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } + "license": "MIT" }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-await-retry": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/async-await-retry/-/async-await-retry-2.1.0.tgz", + "integrity": "sha512-eP0cVR8SfTussawaGL1edKe6aQJiVo2A8+TFBhpg3GKMHcn3FvAT/CfdaPtXdbsLsca/L7qwhi7e8D7SDFnoNQ==", + "license": "MIT", + "bin": { + "async-await-retry": "index.js" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=7.6.0" } }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", "dev": true, "license": "MIT", "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "tslib": "^2.4.0" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "safe-buffer": "5.1.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8" } }, - "node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, + "license": "Apache-2.0" + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/globby/node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/globby/node_modules/ignore": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", - "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, "engines": { - "node": ">= 4" + "node": ">=8" } }, - "node_modules/globby/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true, - "license": "MIT", + "license": "ISC" + }, + "node_modules/bson": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", + "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=20.19.0" } }, - "node_modules/globby/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10.16.0" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" }, "bin": { - "handlebars": "bin/handlebars" + "c8": "bin/c8.js" }, "engines": { - "node": ">=0.4.7" + "node": ">=18" }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, - "node_modules/has-flag": { - "version": "4.0.0", + "node_modules/c8/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", + "node_modules/c8/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", "dependencies": { - "has-symbols": "^1.0.3" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/heap": { - "version": "0.2.7", - "dev": true, - "license": "MIT" - }, - "node_modules/helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "node_modules/c8/node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/hook-std": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", - "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "node_modules/c8/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hosted-git-info": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz", - "integrity": "sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg==", + "node_modules/c8/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-escaper": { - "version": "2.0.2", + "node_modules/c8/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" }, - "engines": { - "node": ">= 0.8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/c8/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "p-limit": "^3.0.2" }, "engines": { - "node": ">= 14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/c8/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/c8/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "license": "ISC", "engines": { - "node": ">=6.0" + "node": ">=14" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", - "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "node_modules/c8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, "engines": { - "node": ">=18.18.0" + "node": ">=18" } }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "node_modules/c8/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "bin": { - "husky": "bin.js" - }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/typicode" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-fresh": { - "version": "3.3.0", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-from-esm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", - "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "import-meta-resolve": "^4.0.0" - }, "engines": { - "node": ">=18.20" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-from-esm/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/import-from-esm/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">=8" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/index-to-position": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", - "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "node_modules/clean-stack/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/into-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", - "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", - "dev": true, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "license": "MIT", "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "string-width": "^4.2.0" }, "engines": { - "node": ">=12" + "node": "10.* || >= 12.*" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "license": "MIT", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, "engines": { - "node": ">= 0.10" + "node": ">=12" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "license": "MIT" - }, - "node_modules/is-extglob": { - "version": "2.1.1", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "color-convert": "^3.1.3", + "color-string": "^2.1.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">=14.6" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.20" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "dev": true, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/is-stream": { - "version": "2.0.0", + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.1.90" } }, - "node_modules/is-text-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", - "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", - "dev": true, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { - "text-extensions": "^2.0.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "dev": true, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.20.0 || >=14" } }, - "node_modules/isarray": { - "version": "1.0.0", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true, "license": "MIT" }, - "node_modules/isexe": { + "node_modules/compare-func": { "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/issue-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", - "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "license": "MIT", "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - }, - "engines": { - "node": "^18.17 || >=20.6.1" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "mime-db": ">= 1.43.0 < 2" }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" } }, - "node_modules/java-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6.0" + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, - "node_modules/jest-diff": { - "version": "29.7.0", + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "safe-buffer": "5.2.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "dev": true, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "compare-func": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=16" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "compare-func": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=16" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, + "node_modules/conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "dev": true, + "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", "dev": true, "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, "bin": { - "jiti": "lib/jiti-cli.mjs" + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" } }, - "node_modules/jose": { - "version": "4.15.9", + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=12" + }, "funding": { - "url": "https://github.com/sponsors/panva" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-tokens": { - "version": "4.0.0", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "license": "MIT", + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "license": "Apache-2.0", "dependencies": { - "argparse": "^2.0.1" + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" + "node_modules/convict/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "node_modules/json-diff": { - "version": "1.0.6", + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", - "dependencies": { - "@ewoudenberg/difflib": "0.1.0", - "colors": "^1.4.0", - "dreamopt": "~0.8.0" - }, - "bin": { - "json-diff": "bin/json-diff.js" - }, "engines": { - "node": "*" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.1.0", + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "object-assign": "^4", + "vary": "^1" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">= 0.10" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, - "license": "(MIT OR Apache-2.0)", + "license": "MIT", "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": "*" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "dev": true, "license": "MIT", "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" + "jiti": "^2.6.1" }, "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/jwa": { - "version": "1.4.1", + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" } }, - "node_modules/jwks-rsa": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", - "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/express": "^4.17.20", - "@types/jsonwebtoken": "^9.0.4", - "debug": "^4.3.4", - "jose": "^4.15.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.2.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=14" + "node": ">= 8" } }, - "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.21", + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jwks-rsa/node_modules/debug": { - "version": "4.3.4", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4.0.0" } }, - "node_modules/jwks-rsa/node_modules/ms": { - "version": "2.1.2", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, - "node_modules/jws": { - "version": "3.2.2", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/jwt-decode": { - "version": "4.0.0", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.8" } }, - "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "license": "Apache-2.0", + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "license": "ISC", "dependencies": { - "json-buffer": "3.0.1" + "asap": "^2.0.0", + "wrappy": "1" } }, - "node_modules/kuler": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/levn": { - "version": "0.4.1", + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.8.0" + "node": ">=0.3.1" } }, - "node_modules/limiter": { - "version": "1.1.5" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "path-type": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "license": "MIT", "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "is-obj": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/dreamopt": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.8.0.tgz", + "integrity": "sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==", "dev": true, - "license": "MIT", "dependencies": { - "p-locate": "^6.0.0" + "wordwrap": ">=0.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.4.0" } }, - "node_modules/lodash": { - "version": "4.17.21", - "license": "MIT" + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "license": "MIT" + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } }, - "node_modules/lodash.capitalize": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", - "dev": true, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "dev": true, "license": "MIT" }, - "node_modules/lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", - "license": "MIT" - }, - "node_modules/lodash.find": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", - "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==", - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "license": "MIT" + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", - "license": "MIT" + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "license": "MIT" + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "license": "MIT" + "node_modules/env-ci": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "license": "MIT" + "node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "license": "MIT" + "node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "node_modules/env-ci/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } }, - "node_modules/lodash.last": { + "node_modules/env-ci/node_modules/is-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", - "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "node_modules/env-ci/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "node_modules/env-ci/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.uniqby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "node_modules/env-ci/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "node_modules/env-ci/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/lodash.values": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", - "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==", - "license": "MIT" + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/log-symbols": { - "version": "4.1.0", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/logform": { - "version": "2.7.0", + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" + "is-arrayish": "^0.2.1" } }, - "node_modules/logform/node_modules/@colors/colors": { - "version": "1.6.0", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": ">= 0.4" } }, - "node_modules/logform/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/long-timeout": { - "version": "0.1.1", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/long-timeout/-/long-timeout-0.1.1.tgz", - "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", - "license": "MIT" + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/lru-memoizer": { - "version": "2.3.0", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "6.0.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } - }, - "node_modules/luxon": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", - "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/make-dir": { + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, "engines": { "node": ">=10" }, @@ -6241,3715 +6415,4081 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, "bin": { - "marked": "bin/marked.js" + "eslint": "bin/eslint.js" }, "engines": { - "node": ">= 18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/marked-terminal": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", - "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "ansi-regex": "^6.1.0", - "chalk": "^5.4.1", - "cli-highlight": "^2.1.11", - "cli-table3": "^0.6.5", - "node-emoji": "^2.2.0", - "supports-hyperlinks": "^3.1.0" + "bin": { + "eslint-config-prettier": "bin/cli.js" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" }, "peerDependencies": { - "marked": ">=1 <16" + "eslint": ">=7.0.0" } }, - "node_modules/marked-terminal/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, "engines": { - "node": ">=12" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/memory-pager": { - "version": "1.5.0", - "license": "MIT" - }, - "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/migrate-mongo": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-12.1.3.tgz", - "integrity": "sha512-UHXkNVVNKaPSXAHvzcCgvc41FpSqUxTlaBNSl+DpGBDxbIZ1B85ql2k2rphXSsh8zKhHS6qHrboLabYuuu/8Eg==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { - "cli-table3": "^0.6.1", - "commander": "^9.1.0", - "date-fns": "^2.28.0", - "fn-args": "^5.0.0", - "fs-extra": "^10.0.1", - "lodash.filter": "^4.6.0", - "lodash.find": "^4.6.0", - "lodash.get": "^4.4.2", - "lodash.isempty": "^4.4.0", - "lodash.last": "^3.0.0", - "lodash.values": "^4.3.0", - "p-each-series": "^2.2.0" - }, - "bin": { - "migrate-mongo": "bin/migrate-mongo.js" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, - "peerDependencies": { - "mongodb": "^4.4.1 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "p-limit": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mime-db": { - "version": "1.52.0", + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", + "node_modules/eslint/node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "punycode": "^2.1.0" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^1.1.7" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/minipass": { - "version": "7.1.2", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=0.10" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "minimist": "^1.2.6" + "estraverse": "^5.2.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=4.0" } }, - "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, + "license": "BSD-2-Clause", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=4.0" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0" + "bare-events": "^2.7.0" } }, - "node_modules/mocha/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/mocha/node_modules/debug": { - "version": "4.4.0", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=6.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, + "node_modules/express-openapi-validator": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.0.tgz", + "integrity": "sha512-gNaMgDb1cAT8QKcuh9WrED9p3mqi/V7yocNrvnE1fOz7e8p8JkbYaTUcOB4VsZKerz/X+Sey7ptTGF5FwsXh8Q==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^14.0.3", + "@types/multer": "^1.4.13", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "json-schema-traverse": "^1.0.0", + "lodash.clonedeep": "^4.5.0", + "lodash.get": "^4.4.2", + "media-typer": "^1.1.0", + "multer": "^2.0.2", + "ono": "^7.1.3", + "path-to-regexp": "^8.2.0", + "qs": "^6.14.0" + }, + "peerDependencies": { + "express": "*" + } + }, + "node_modules/express-openapi-validator/node_modules/@apidevtools/json-schema-ref-parser": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", + "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", "license": "MIT", + "dependencies": { + "js-yaml": "^4.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 20" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/philsturgeon" + }, + "peerDependencies": { + "@types/json-schema": "^7.0.15" } }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "dev": true, + "node_modules/express-openapi-validator/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8.0" } }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "ms": "2.0.0" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "dev": true, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "ms": "2.0.0" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/mongodb": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", - "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.3", - "mongodb-connection-string-url": "^3.0.0" - }, + "node_modules/express/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" - } + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/mongodb-memory-server": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.1.4.tgz", - "integrity": "sha512-+oKQ/kc3CX+816oPFRtaF0CN4vNcGKNjpOQe4bHo/21A3pMD+lC7Xz1EX5HP7siCX4iCpVchDMmCOFXVQSGkUg==", - "dev": true, - "hasInstallScript": true, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "mongodb-memory-server-core": "10.1.4", - "tslib": "^2.7.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=16.20.1" + "node": ">= 0.8" } }, - "node_modules/mongodb-memory-server-core": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.1.4.tgz", - "integrity": "sha512-o8fgY7ZalEd8pGps43fFPr/hkQu1L8i6HFEGbsTfA2zDOW0TopgpswaBCqDr0qD7ptibyPfB5DmC+UlIxbThzA==", - "dev": true, + "node_modules/express/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { - "async-mutex": "^0.5.0", - "camelcase": "^6.3.0", - "debug": "^4.3.7", - "find-cache-dir": "^3.3.2", - "follow-redirects": "^1.15.9", - "https-proxy-agent": "^7.0.5", - "mongodb": "^6.9.0", - "new-find-package-json": "^2.0.0", - "semver": "^7.6.3", - "tar-stream": "^3.1.7", - "tslib": "^2.7.0", - "yauzl": "^3.1.3" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { - "node": ">=16.20.1" + "node": ">= 0.6" } }, - "node_modules/mongodb-memory-server-core/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, "engines": { - "node": ">= 14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mongodb-memory-server-core/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/figures/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mongodb-memory-server-core/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">= 14" + "node": ">=16.0.0" } }, - "node_modules/mongodb-memory-server-core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT" - }, - "node_modules/mongoose": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.1.tgz", - "integrity": "sha512-RhQ4DzmBi5BNGcS0w4u1vdMRIKcteXTCNzDt1j7XRcdWYBz1MjMjulBhPaeC5jBCHOD1yinuOFTTSOWLLGexWw==", "license": "MIT", "dependencies": { - "bson": "^6.10.3", - "kareem": "2.6.3", - "mongodb": "~6.16.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" + "node": ">=8" } }, - "node_modules/mongoose/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { - "basic-auth": "~2.0.1", "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.1.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, - "node_modules/mpath": { - "version": "0.9.0", + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "engines": { - "node": ">=4.0.0" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "4.x" + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/mquery/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "semver": "^6.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mquery/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.0.0", - "license": "MIT" + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/multer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz", - "integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==", + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, "license": "MIT", "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">= 10.16.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/multer/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/multer/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "dev": true, "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" } }, - "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^18 || >=20" + "node": ">=16" } }, - "node_modules/natural-compare": { - "version": "1.4.0", + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/fn-args": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", + "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/nerf-dart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", - "dev": true, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, - "node_modules/new-find-package-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", - "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/new-find-package-json/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=4.0" }, "peerDependenciesMeta": { - "supports-color": { + "debug": { "optional": true } } }, - "node_modules/new-find-package-json/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-cache": { - "version": "5.1.2", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { - "clone": "2.x" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 6" } }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "dev": true, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" }, "engines": { - "node": ">=18" + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/node-schedule": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", - "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "dependencies": { - "cron-parser": "^4.2.0", - "long-timeout": "0.1.1", - "sorted-array-functions": "^1.3.0" - }, "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "license": "ISC", + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", "dependencies": { - "lru-cache": "^10.0.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", - "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.0", - "@npmcli/config": "^9.0.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.1.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "@sigstore/tuf": "^3.0.0", - "abbrev": "^3.0.0", - "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.3.0", - "ci-info": "^4.1.0", - "cli-columns": "^4.0.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^10.4.5", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.0.2", - "ini": "^5.0.0", - "init-package-json": "^7.0.2", - "is-cidr": "^5.1.0", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.0", - "libnpmexec": "^9.0.0", - "libnpmfund": "^6.0.0", - "libnpmhook": "^11.0.0", - "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.0", - "libnpmpublish": "^10.0.1", - "libnpmsearch": "^8.0.0", - "libnpmteam": "^7.0.0", - "libnpmversion": "^7.0.0", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^11.0.0", - "nopt": "^8.0.0", - "normalize-package-data": "^7.0.0", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^4.0.0", - "pacote": "^19.0.1", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "qrcode-terminal": "^0.12.0", - "read": "^4.0.0", - "semver": "^7.6.3", - "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^9.4.0", - "tar": "^6.2.1", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.0", - "which": "^5.0.0", - "write-file-atomic": "^6.0.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" } }, - "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" + "pump": "^3.0.0" }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/git-log-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", + "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "0.6.8" } }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/git-log-parser/node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" + "readable-stream": "^2.0.2" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", + "node_modules/git-log-parser/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/git-log-parser/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "through2": "~2.0.0" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/git-log-parser/node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" } }, - "node_modules/npm/node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", + "node_modules/git-log-parser/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" + "safe-buffer": "~5.1.0" } }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", + "node_modules/git-log-parser/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } }, - "node_modules/npm/node_modules/@npmcli/agent": { - "version": "3.0.0", + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16" } }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.0", + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/metavuln-calculator": "^8.0.0", - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.1", - "@npmcli/query": "^4.0.0", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "bin-links": "^5.0.0", - "cacache": "^19.0.1", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", "minimatch": "^9.0.4", - "nopt": "^8.0.0", - "npm-install-checks": "^7.1.0", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.1", - "pacote": "^19.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "proggy": "^3.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "ssri": "^12.0.0", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { - "arborist": "bin/index.js" + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10.13.0" } }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "9.0.0", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", - "ci-info": "^4.0.0", - "ini": "^5.0.0", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "walk-up-path": "^3.0.1" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "4.0.0", + "node_modules/glob/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "semver": "^7.3.5" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.1", + "node_modules/glob/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^5.0.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", + "node_modules/glob/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "inBundle": true, "license": "ISC", - "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.2", + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0" + "ini": "4.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "8.0.1", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cacache": "^19.0.0", - "json-parse-even-better-errors": "^4.0.0", - "pacote": "^20.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5" + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { - "version": "20.0.0", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, "bin": { - "pacote": "bin/index.js" + "handlebars": "bin/handlebars" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { + "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "normalize-package-data": "^7.0.0", - "proc-log": "^5.0.0", - "semver": "^7.5.3" - }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "which": "^5.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "function-bind": "^1.1.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" } }, - "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.0.0", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "license": "MIT", + "bin": { + "he": "bin/he" } }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.0.2", + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" - }, + "license": "MIT" + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18.0.0" } }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=14" + "node": "*" } }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", + "node_modules/hook-std": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", + "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "license": "ISC", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^3.0.1" + "lru-cache": "^10.0.1" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } + "license": "ISC" }, - "node_modules/npm/node_modules/abbrev": { - "version": "3.0.0", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/npm/node_modules/agent-base": { - "version": "7.1.1", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { + "agent-base": "^7.1.0", "debug": "^4.3.4" }, "engines": { "node": ">= 14" } }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=8.12.0" } }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, - "inBundle": true, "license": "MIT", + "bin": { + "husky": "bin.js" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bin-links": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", "dependencies": { - "cmd-shim": "^7.0.0", - "npm-normalize-package-bin": "^4.0.0", - "proc-log": "^5.0.0", - "read-cmd-shim": "^5.0.0", - "write-file-atomic": "^6.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "2.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=0.10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">= 4" } }, - "node_modules/npm/node_modules/cacache": { - "version": "19.0.1", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/npm/node_modules/cacache/node_modules/minizlib": { - "version": "3.0.1", + "node_modules/import-from-esm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", + "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">= 18" + "node": ">=18.20" } }, - "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, - "inBundle": true, "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/npm/node_modules/cacache/node_modules/p-map": { - "version": "7.0.2", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" + "node": ">=0.8.19" } }, - "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "node_modules/indent-string": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, - "inBundle": true, "license": "ISC", "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ci-info": { - "version": "4.1.0", + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", "dependencies": { - "ip-regex": "^5.0.0" + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" }, "engines": { - "node": ">=14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", + "node_modules/into-stream/node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", + "node_modules/into-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "7.0.0", + "node_modules/into-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", + "node_modules/into-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">= 0.10" } }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "inBundle": true, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "inBundle": true, "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, "engines": { - "node": ">=4" + "node": ">=0.12.0" } }, - "node_modules/npm/node_modules/debug": { - "version": "4.3.7", + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/npm/node_modules/diff": { - "version": "5.2.0", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "inBundle": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">=8" } }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", "dev": true, - "inBundle": true, "license": "MIT", - "optional": true, "dependencies": { - "iconv-lite": "^0.6.2" + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "inBundle": true, - "license": "Apache-2.0" + "license": "ISC" }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", + "node_modules/issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, "engines": { - "node": ">= 4.9.1" + "node": "^18.17 || >=20.6.1" } }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.0", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "minipass": "^7.0.3" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/npm/node_modules/glob": { - "version": "10.4.5", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { + "node_modules/jackspeak/node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12" + } + }, + "node_modules/jackspeak/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", + "node_modules/jackspeak/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "inBundle": true, - "license": "BSD-2-Clause" + "license": "MIT" }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.2", + "node_modules/jackspeak/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.5", + "node_modules/jackspeak/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", + "node_modules/jackspeak/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "inBundle": true, "license": "MIT", - "optional": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^9.0.0" + "node": ">=12" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">= 0.6.0" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ini": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/init-package-json": { - "version": "7.0.2", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/package-json": "^6.0.0", - "npm-package-arg": "^12.0.0", - "promzard": "^2.0.0", - "read": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^6.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.0", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "^4.1.1" - }, + "license": "MIT", "engines": { - "node": ">=14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "3.4.3", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "9.0.0", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.0", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "binary-extensions": "^2.3.0", - "diff": "^5.1.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "tar": "^6.2.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/run-script": "^9.0.1", - "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "proc-log": "^5.0.0", - "read": "^4.0.0", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "walk-up-path": "^3.0.1" + "node": ">=10" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.0", + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/arborist": "^8.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "11.0.0", + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "7.0.0", + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.0", + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/run-script": "^9.0.1", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "10.0.1", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/json-diff": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-1.0.6.tgz", + "integrity": "sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==", + "dev": true, + "license": "MIT", "dependencies": { - "ci-info": "^4.0.0", - "normalize-package-data": "^7.0.0", - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1", - "proc-log": "^5.0.0", - "semver": "^7.3.7", - "sigstore": "^3.0.0", - "ssri": "^12.0.0" + "@ewoudenberg/difflib": "0.1.0", + "colors": "^1.4.0", + "dreamopt": "~0.8.0" + }, + "bin": { + "json-diff": "bin/json-diff.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "*" } }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "8.0.0", + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", "dependencies": { - "npm-registry-fetch": "^18.0.1" + "universalify": "^2.0.0" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "7.0.0", + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true, - "inBundle": true, - "license": "ISC", + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "*" } }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", "dependencies": { - "@npmcli/git": "^6.0.1", - "@npmcli/run-script": "^9.0.1", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.7" + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/npm/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "inBundle": true, - "license": "ISC" + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "14.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "license": "MIT", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", - "dev": true, - "inBundle": true, + "node_modules/jwks-rsa/node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" } }, - "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=18" } }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12.0.0" } }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.0", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" + "json-buffer": "3.0.1" } }, - "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { - "version": "3.0.1", + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">= 18" + "node": ">= 0.8.0" } }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "license": "MIT" + }, + "node_modules/lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.last": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", + "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/mute-stream": { - "version": "2.0.0", + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/npm/node_modules/node-gyp": { - "version": "11.0.0", + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "which": "^5.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { - "version": "3.0.1", + "node_modules/lodash.values": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", + "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">= 18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, "engines": { - "node": ">=18" + "node": ">= 12.0.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=0.1.90" } }, - "node_modules/npm/node_modules/nopt": { - "version": "8.0.0", - "dev": true, - "inBundle": true, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "yallist": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/nopt/node_modules/abbrev": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" } }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12" } }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "4.0.0", + "node_modules/make-asynchronous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", + "integrity": "sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "1.2.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" + "node": ">=18" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { + "node_modules/make-dir": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.0", + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" + "license": "MIT", + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 18" } }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "9.0.0", + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "ignore-walk": "^7.0.0" + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" } }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "10.0.0", + "node_modules/marked-terminal/node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", - "semver": "^7.3.5" + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/npm/node_modules/npm-profile": { - "version": "11.0.1", + "node_modules/marked-terminal/node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "18.0.2", + "node_modules/marked-terminal/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^3.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { - "version": "3.0.1", + "node_modules/marked-terminal/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/marked-terminal/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" + "parse5": "^6.0.1" } }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "3.0.0", + "node_modules/marked-terminal/node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "node_modules/marked-terminal/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "aggregate-error": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.1", + "node_modules/marked-terminal/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0" + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "node_modules/npm/node_modules/pacote": { - "version": "19.0.1", + "node_modules/marked-terminal/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" } }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "4.0.0", + "node_modules/marked-terminal/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "inBundle": true, "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" } }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "inBundle": true, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.8" } }, - "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.1.2", + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/proc-log": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/proggy": { - "version": "3.0.0", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" } }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node_modules/migrate-mongo": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-12.1.3.tgz", + "integrity": "sha512-UHXkNVVNKaPSXAHvzcCgvc41FpSqUxTlaBNSl+DpGBDxbIZ1B85ql2k2rphXSsh8zKhHS6qHrboLabYuuu/8Eg==", + "license": "MIT", + "dependencies": { + "cli-table3": "^0.6.1", + "commander": "^9.1.0", + "date-fns": "^2.28.0", + "fn-args": "^5.0.0", + "fs-extra": "^10.0.1", + "lodash.filter": "^4.6.0", + "lodash.find": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.isempty": "^4.4.0", + "lodash.last": "^3.0.0", + "lodash.values": "^4.3.0", + "p-each-series": "^2.2.0" + }, + "bin": { + "migrate-mongo": "bin/migrate-mongo.js" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "mongodb": "^4.4.1 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "inBundle": true, + "node_modules/migrate-mongo/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "license": "MIT", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "@babel/runtime": "^7.21.0" }, "engines": { - "node": ">=10" + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" } }, - "node_modules/npm/node_modules/promzard": { - "version": "2.0.0", + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "^4.0.0" + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16" } }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/read": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { - "mute-stream": "^2.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "5.0.0", + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=6" } }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "4.0.0", + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "*" } }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "dev": true, - "inBundle": true, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", - "engines": { - "node": ">= 4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/rimraf": { - "version": "5.0.10", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "inBundle": true, "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", "dependencies": { - "glob": "^10.3.7" + "minimist": "^1.2.6" }, "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "mkdirp": "bin/cmd.js" } }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, - "inBundle": true, "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.6.3", - "dev": true, - "inBundle": true, - "license": "ISC", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, "bin": { - "semver": "bin/semver.js" + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0" } }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=14" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/sigstore": { - "version": "3.0.0", + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^3.0.0", - "@sigstore/tuf": "^3.0.0", - "@sigstore/verify": "^2.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.0.0", + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { - "version": "2.0.0", + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.0.0", + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^14.0.1", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" + "p-limit": "^3.0.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.0.0", + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/npm/node_modules/socks": { - "version": "2.8.3", + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", + "node_modules/mongodb": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", + "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.0.0", + "mongodb-connection-string-url": "^7.0.0" }, "engines": { - "node": ">= 14" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, + "node_modules/mongodb-connection-string-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", + "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" } }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/mongodb-memory-server": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.1.tgz", + "integrity": "sha512-XpCyV1e7QQ1lW28rgtXP4ZlX8ZfD/8z1ZGNxz2y3JrosLgDrNnYWvPjlgFj3JjboYUtlh1jF2Ez/rwsQA6cl0w==", "dev": true, - "inBundle": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "mongodb-memory-server-core": "10.4.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.20.1" } }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "4.0.0", + "node_modules/mongodb-memory-server/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "@types/webidl-conversions": "*" } }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.20", - "dev": true, - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/npm/node_modules/ssri": { - "version": "12.0.0", + "node_modules/mongodb-memory-server/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16.20.1" } }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", + "node_modules/mongodb-memory-server/node_modules/mongodb": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", + "integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", + "node_modules/mongodb-memory-server/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/mongodb-memory-server/node_modules/mongodb-memory-server-core": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.1.tgz", + "integrity": "sha512-YJdrEyF9hk64nfeoVDMP6IfTzK+gLZhrQqYyP6JJMsqo2LK5eF7JRZ4YPQDmt1re/JhItpiU+ypiZbIG1OsW5Q==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "async-mutex": "^0.5.0", + "camelcase": "^6.3.0", + "debug": "^4.4.3", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.11", + "https-proxy-agent": "^7.0.6", + "mongodb": "^6.9.0", + "new-find-package-json": "^2.0.0", + "semver": "^7.7.3", + "tar-stream": "^3.1.7", + "tslib": "^2.8.1", + "yauzl": "^3.2.0" }, "engines": { - "node": ">=8" + "node": ">=16.20.1" } }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "inBundle": true, + "node_modules/mongoose": { + "version": "8.20.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.2.tgz", + "integrity": "sha512-U0TPupnqBOAI3p9H9qdShX8/nJUBylliRcHFKuhbewEkM7Y0qc9BbrQR9h4q6+1easoZqej7cq2Ee36AZ0gMzQ==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" }, "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" + "node": ">=16.20.1" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/mongoose" } }, - "node_modules/npm/node_modules/tar": { - "version": "6.2.1", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": ">=16.20.1" } }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", "dependencies": { - "minipass": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { - "node": ">= 8" + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" } }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", - "dev": true, - "inBundle": true, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "3.0.1", - "dev": true, - "inBundle": true, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" + "ee-first": "1.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8" } }, - "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { - "version": "3.0.1", - "dev": true, - "inBundle": true, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=4.0.0" } }, - "node_modules/npm/node_modules/unique-filename": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", "dependencies": { - "unique-slug": "^5.0.0" + "debug": "4.x" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14.0.0" } }, - "node_modules/npm/node_modules/unique-slug": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4" + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 10.16.0" } }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" } }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^18 || >=20" } }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/npm/node_modules/which": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.6" } }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" - } + "license": "MIT" }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } + "license": "MIT" }, - "node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", + "node_modules/new-find-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "debug": "^4.3.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=12.22.0" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "inBundle": true, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "clone": "2.x" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 8.0.0" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=18" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "inBundle": true, + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "6.0.0", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" + "path-key": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9968,16 +10508,18 @@ } }, "node_modules/oidc-token-hash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", - "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", "license": "MIT", "engines": { "node": "^10.13.0 || >=12.0.0" } }, "node_modules/on-finished": { - "version": "2.3.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -9997,6 +10539,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -10004,22 +10548,24 @@ }, "node_modules/one-time": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", "dependencies": { "fn.name": "1.x.x" } }, "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=12" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10027,6 +10573,8 @@ }, "node_modules/ono": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ==", "license": "MIT", "dependencies": { "@jsdevtools/ono": "7.1.3" @@ -10034,6 +10582,8 @@ }, "node_modules/openapi-schema-validator": { "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-12.1.3.tgz", + "integrity": "sha512-xTHOmxU/VQGUgo7Cm0jhwbklOKobXby+/237EG967+3TQEYJztMgX9Q5UE2taZKwyKPUq0j11dngpGjUuxz1hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10043,8 +10593,28 @@ "openapi-types": "^12.1.3" } }, + "node_modules/openapi-schema-validator/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/openapi-types": { "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "dev": true, "license": "MIT" }, @@ -10063,8 +10633,19 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -10081,6 +10662,8 @@ }, "node_modules/p-each-series": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "license": "MIT", "engines": { "node": ">=8" @@ -10089,6 +10672,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-filter": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", @@ -10131,19 +10730,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-limit/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-locate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", @@ -10161,9 +10747,9 @@ } }, "node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { @@ -10186,23 +10772,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -10257,30 +10860,17 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, "node_modules/parse5-query-domtree": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse5-query-domtree/-/parse5-query-domtree-1.0.2.tgz", + "integrity": "sha512-5mmp13wtARQonN1RInX4R3p5Jx2AQtd2lYlTMk6G84ZZBUIHMNpvMmTzkEZ+kRbIvZDwX8uQopZIrFkawfJy3Q==", "dev": true, "license": "ISC" }, "node_modules/parseurl": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10288,6 +10878,8 @@ }, "node_modules/passport": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", "dependencies": { "passport-strategy": "1.x.x", @@ -10304,6 +10896,8 @@ }, "node_modules/passport-anonym-uuid": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/passport-anonym-uuid/-/passport-anonym-uuid-1.0.3.tgz", + "integrity": "sha512-bsu59r5hyU0zwqJ3EMdbN0O7O4oZmcjFNhVKXF2PAgPlm1TTJUizJnrkErj4mjOM/mtNE4FwbW4TusrXZJ4lLQ==", "dependencies": { "node-uuid": "^1.4.7", "passport-strategy": "1.x.x" @@ -10312,14 +10906,10 @@ "node": ">= 0.4.0" } }, - "node_modules/passport-anonym-uuid/node_modules/node-uuid": { - "version": "1.4.8", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/passport-http": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/passport-http/-/passport-http-0.3.0.tgz", + "integrity": "sha512-OwK9DkqGVlJfO8oD0Bz1VDIo+ijD3c1ZbGGozIZw+joIP0U60pXY7goB+8wiDWtNqHpkTaQiJ9Ux1jE3Ykmpuw==", "dependencies": { "passport-strategy": "1.x.x" }, @@ -10329,6 +10919,8 @@ }, "node_modules/passport-http-bearer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz", + "integrity": "sha512-SELQM+dOTuMigr9yu8Wo4Fm3ciFfkMq5h/ZQ8ffi4ELgZrX1xh9PlglqZdcUZ1upzJD/whVyt+YWF62s3U6Ipw==", "dependencies": { "passport-strategy": "1.x.x" }, @@ -10338,54 +10930,37 @@ }, "node_modules/passport-strategy": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", "engines": { "node": ">= 0.4.0" } }, "node_modules/path-exists": { - "version": "4.0.0", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "license": "ISC" - }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -10398,7 +10973,9 @@ } }, "node_modules/pause": { - "version": "0.0.1" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/pend": { "version": "1.2.0", @@ -10409,11 +10986,15 @@ }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -10500,16 +11081,6 @@ "node": ">=4" } }, - "node_modules/pkg-conf/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -10589,8 +11160,30 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -10598,9 +11191,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", "bin": { @@ -10615,6 +11208,8 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "license": "MIT", "dependencies": { @@ -10624,23 +11219,10 @@ "node": ">=6.0.0" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/pretty-ms": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", - "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10655,6 +11237,8 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "license": "MIT" }, @@ -10667,6 +11251,8 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -10695,6 +11281,8 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -10715,29 +11303,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/random-bytes": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10745,6 +11314,8 @@ }, "node_modules/randombytes": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10761,18 +11332,18 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, "node_modules/rc": { @@ -10791,6 +11362,13 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -10803,11 +11381,15 @@ }, "node_modules/react-is": { "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, "node_modules/read-json-lines-sync": { "version": "2.2.5", + "resolved": "https://registry.npmjs.org/read-json-lines-sync/-/read-json-lines-sync-2.2.5.tgz", + "integrity": "sha512-yTQK/fkO0ZIKSMC26F3OKHwVnUZ9PVLSh0flrliwpBgoWPxck5SK3qIlZEfrBb1Uahh518p4637pK7e0EfutrQ==", "dev": true, "license": "MIT" }, @@ -10849,30 +11431,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", - "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/code-frame": "^7.22.13", - "index-to-position": "^0.1.2", - "type-fest": "^4.7.1" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=18" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/read-pkg/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/read-pkg/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, "engines": { "node": ">=18" }, @@ -10881,17 +11485,17 @@ } }, "node_modules/readable-stream": { - "version": "2.3.7", - "dev": true, + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/readdirp": { @@ -10915,255 +11519,492 @@ "dev": true, "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^2.1.0" + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semantic-release": { + "version": "24.2.9", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.9.tgz", + "integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/commit-analyzer": "^13.0.0-beta.1", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^11.0.0", + "@semantic-release/npm": "^12.0.2", + "@semantic-release/release-notes-generator": "^14.0.0-beta.1", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^9.0.0", + "figures": "^6.0.0", + "find-versions": "^6.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^4.0.0", + "hosted-git-info": "^8.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "marked": "^15.0.0", + "marked-terminal": "^7.3.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-package-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^5.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": ">=20.8.1" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/semantic-release/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/require-directory": { - "version": "2.1.1", + "node_modules/semantic-release/node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/require-from-string": { - "version": "2.0.2", + "node_modules/semantic-release/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/resolve-from": { - "version": "4.0.0", + "node_modules/semantic-release/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/semantic-release/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/semantic-release/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "license": "MIT", + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { "node": ">=10" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semantic-release": { - "version": "24.2.5", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.5.tgz", - "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==", + "node_modules/semver-diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-5.0.0.tgz", + "integrity": "sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg==", + "deprecated": "Deprecated as the semver package now supports this built-in.", "dev": true, "license": "MIT", "dependencies": { - "@semantic-release/commit-analyzer": "^13.0.0-beta.1", - "@semantic-release/error": "^4.0.0", - "@semantic-release/github": "^11.0.0", - "@semantic-release/npm": "^12.0.0", - "@semantic-release/release-notes-generator": "^14.0.0-beta.1", - "aggregate-error": "^5.0.0", - "cosmiconfig": "^9.0.0", - "debug": "^4.0.0", - "env-ci": "^11.0.0", - "execa": "^9.0.0", - "figures": "^6.0.0", - "find-versions": "^6.0.0", - "get-stream": "^6.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^3.0.0", - "hosted-git-info": "^8.0.0", - "import-from-esm": "^2.0.0", - "lodash-es": "^4.17.21", - "marked": "^15.0.0", - "marked-terminal": "^7.3.0", - "micromatch": "^4.0.2", - "p-each-series": "^3.0.0", - "p-reduce": "^3.0.0", - "read-package-up": "^11.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.2", - "semver-diff": "^4.0.0", - "signale": "^1.2.1", - "yargs": "^17.5.1" - }, - "bin": { - "semantic-release": "bin/semantic-release.js" + "semver": "^7.3.5" }, "engines": { - "node": ">=20.8.1" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "license": "MIT", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "node_modules/send": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", + "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.8.0" } }, - "node_modules/semantic-release/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/semantic-release/node_modules/p-each-series": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", - "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", - "dev": true, + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, "engines": { - "node": ">=12" + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4" } }, - "node_modules/semantic-release/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/semantic-release/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, "engines": { - "node": ">=12" + "node": ">= 0.8.0" } }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dev": true, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/semver-regex": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", - "dev": true, + "node_modules/serve-static/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", - "engines": { - "node": ">=12" + "bin": { + "mime": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4" } }, - "node_modules/send": { + "node_modules/serve-static/node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", @@ -11187,7 +12028,7 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", @@ -11196,58 +12037,32 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/serve-static/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, "engines": { "node": ">= 0.8" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/set-cookie-parser": { - "version": "2.7.1", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "dev": true, "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -11259,6 +12074,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -11344,15 +12161,11 @@ "license": "MIT" }, "node_modules/signal-exit": { - "version": "4.1.0", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/signale": { "version": "1.4.0", @@ -11397,6 +12210,23 @@ "node": ">=4" } }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, "node_modules/signale/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -11443,13 +12273,6 @@ "node": ">=4" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/sinon": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", @@ -11483,6 +12306,8 @@ }, "node_modules/slash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { @@ -11491,7 +12316,7 @@ }, "node_modules/sorted-array-functions": { "version": "1.3.0", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", "license": "MIT" }, @@ -11507,6 +12332,8 @@ }, "node_modules/sparse-bitfield": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" @@ -11549,24 +12376,26 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/split2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, "license": "ISC", - "dependencies": { - "through2": "~2.0.0" + "engines": { + "node": ">= 10.x" } }, "node_modules/stack-trace": { "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "license": "MIT", "engines": { "node": "*" @@ -11574,6 +12403,8 @@ }, "node_modules/stack-utils": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11583,22 +12414,23 @@ "node": ">=10" } }, - "node_modules/statuses": { - "version": "2.0.1", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", - "dev": true, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", - "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" + "engines": { + "node": ">= 0.8" } }, "node_modules/streamsearch": { @@ -11610,28 +12442,30 @@ } }, "node_modules/streamx": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", - "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, "license": "MIT", "dependencies": { + "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { - "version": "1.1.1", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11645,6 +12479,8 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -11658,6 +12494,8 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11669,6 +12507,8 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -11678,6 +12518,25 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11689,20 +12548,19 @@ } }, "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -11713,13 +12571,14 @@ } }, "node_modules/super-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", - "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", "dev": true, "license": "MIT", "dependencies": { "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", "time-span": "^5.1.0" }, "engines": { @@ -11730,42 +12589,29 @@ } }, "node_modules/superagent": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.2.tgz", - "integrity": "sha512-vWMq11OwWCC84pQaFPzF/VO3BrjkCeewuvJgt1jfV0499Z1QSAWN4EqfMM5WlFDDX9/oP8JjlDKpblrmEoyu4Q==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "license": "MIT", "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0" + "qs": "^6.11.2" }, "engines": { "node": ">=14.18.0" } }, - "node_modules/superagent/node_modules/debug": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -11774,19 +12620,15 @@ "node": ">=4.0.0" } }, - "node_modules/superagent/node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, "node_modules/supertest": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.1.tgz", - "integrity": "sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, "license": "MIT", "dependencies": { "methods": "^1.1.2", - "superagent": "^10.2.1" + "superagent": "^10.2.3" }, "engines": { "node": ">=14.18.0" @@ -11794,6 +12636,8 @@ }, "node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -11821,7 +12665,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.18.2", + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz", + "integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -11829,6 +12675,8 @@ }, "node_modules/swagger-ui-express": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", "license": "MIT", "dependencies": { "swagger-ui-dist": ">=5.0.0" @@ -11841,121 +12689,41 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^3.0.0", - "temp-dir": "^3.0.0", - "type-fest": "^2.12.2", - "unique-string": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "7.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" + }, + "funding": { + "url": "https://opencollective.com/synckit" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.1", + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.16" } }, "node_modules/text-decoder": { @@ -11983,6 +12751,8 @@ }, "node_modules/text-hex": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, "node_modules/thenify": { @@ -12015,17 +12785,6 @@ "dev": true, "license": "MIT" }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", @@ -12043,14 +12802,19 @@ } }, "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12062,19 +12826,23 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/tr46": { - "version": "4.1.1", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/traverse": { @@ -12092,6 +12860,8 @@ }, "node_modules/triple-beam": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "license": "MIT", "engines": { "node": ">= 14.0.0" @@ -12099,6 +12869,8 @@ }, "node_modules/tslib": { "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, @@ -12114,6 +12886,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -12134,9 +12908,9 @@ } }, "node_modules/type-fest": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", - "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -12160,25 +12934,20 @@ "node": ">= 0.6" } }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typedarray": { @@ -12188,9 +12957,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -12218,6 +12987,8 @@ }, "node_modules/uid-safe": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" @@ -12239,6 +13010,22 @@ "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/undici/node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", @@ -12250,9 +13037,9 @@ } }, "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, "license": "MIT", "engines": { @@ -12278,15 +13065,46 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unique-string/node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", "dev": true, "license": "ISC" }, "node_modules/universalify": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -12301,16 +13119,6 @@ "node": ">= 0.8" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -12323,10 +13131,14 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -12346,13 +13158,15 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" @@ -12371,31 +13185,46 @@ }, "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "license": "BSD-2-Clause", "engines": { "node": ">=12" } }, "node_modules/whatwg-url": { - "version": "13.0.0", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -12409,11 +13238,13 @@ } }, "node_modules/winston": { - "version": "3.17.0", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", + "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", + "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", @@ -12430,6 +13261,8 @@ }, "node_modules/winston-transport": { "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { "logform": "^2.7.0", @@ -12440,39 +13273,19 @@ "node": ">= 12.0.0" } }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "3.6.2", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/winston/node_modules/@colors/colors": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", "engines": { "node": ">=0.1.90" } }, - "node_modules/winston/node_modules/readable-stream": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -12481,33 +13294,23 @@ }, "node_modules/wordwrap": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/workerpool": { - "version": "6.5.1", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true, "license": "Apache-2.0" }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -12522,108 +13325,50 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/xtend": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "license": "MIT", "engines": { "node": ">=0.4" } }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, "node_modules/yargs": { - "version": "16.2.0", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -12632,6 +13377,8 @@ }, "node_modules/yargs-unparser": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "license": "MIT", "dependencies": { @@ -12644,19 +13391,10 @@ "node": ">=10" } }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", + "node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -12678,20 +13416,22 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/yoctocolors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", - "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "dev": true, "license": "MIT", "engines": { @@ -12702,9 +13442,9 @@ } }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" From 42099e623258c489fabf796a2e4aa606284cd375 Mon Sep 17 00:00:00 2001 From: adpare Date: Sat, 6 Dec 2025 18:52:06 -0500 Subject: [PATCH 142/370] chore: replace adm validation logic with pre-existing functions --- app/models/subschemas/attack-pattern.js | 5 +- .../adm-validation-middleware.spec.js | 94 +++---------------- app/tests/shared/clone-for-create.js | 4 + 3 files changed, 22 insertions(+), 81 deletions(-) diff --git a/app/models/subschemas/attack-pattern.js b/app/models/subschemas/attack-pattern.js index c1f8745d..ee64a7cb 100644 --- a/app/models/subschemas/attack-pattern.js +++ b/app/models/subschemas/attack-pattern.js @@ -7,7 +7,10 @@ module.exports.attackPattern = { modified: { type: Date, required: true }, name: { type: String, required: true }, description: String, - kill_chain_phases: [stixCore.killChainPhaseSchema], + kill_chain_phases: { + type: [stixCore.killChainPhaseSchema], + default: undefined + }, // ATT&CK custom STIX properties x_mitre_attack_spec_version: String, diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index a03b6d9d..03651deb 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -11,6 +11,7 @@ logger.level = 'debug'; const uuid = require('uuid'); const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/dist/generator'); +const { cloneForCreate } = require('../shared/clone-for-create') /** * Smoke tests for ATT&CK Data Model (ADM) validation middleware. @@ -133,48 +134,6 @@ describe('ADM Validation Middleware', function () { return syntheticStix; } - /** - * Filters the properties of an object, returning a new object containing only - * those entries whose values pass the validity check. - * @param {Object} obj - The object to filter. - * @returns {Object} - A new object with only the valid entries. - */ - function filterObject(obj) { - const out = {}; - for (const [key, value] of Object.entries(obj)) { - const cleaned = clean(value); - if (cleaned !== undefined) out[key] = cleaned; - } - return out; - } - - /** - * Checks if the provided field has a valid value. - * Returns undefined or the value of the field if it is valid - * @param {*} field - The value to validate. - * @returns {value} - Value if the field has a valid value, undefined otherwise. - */ - function clean(value) { - if (value == null) return undefined; // null or undefined - if (typeof value === 'string' && value.trim() === '') return undefined; - if (typeof value === 'number' && Number.isNaN(value)) return undefined; - if (Array.isArray(value)) { - if (value.length === 0) { - return undefined; - } else { - const arr = value.map((v) => clean(v)).filter((v) => v !== undefined); - return arr.length ? arr : undefined; - } - } - - if (typeof value === 'object') { - const obj = filterObject(value); - return Object.keys(obj).length ? obj : undefined; - } - - return value; - } - before(async function () { // Enable ADM validation and disable OpenAPI validation config.validateRequests.withAttackDataModel = true; @@ -203,7 +162,7 @@ describe('ADM Validation Middleware', function () { it('should accept valid complete data in work-in-progress state', async function () { const syntheticStix = createSyntheticStix(stixType); - const requestBody = { + let requestBody = { type: 'attack-pattern', status: 'work-in-progress', workspace: { @@ -214,7 +173,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; - delete requestBody.stix.external_references; + requestBody = cloneForCreate(requestBody); const res = await request(app) .post(endpoint) @@ -237,7 +196,7 @@ describe('ADM Validation Middleware', function () { delete syntheticStix.x_mitre_platforms; delete syntheticStix.x_mitre_data_sources; - const requestBody = { + let requestBody = { workspace: { workflow: { state: 'work-in-progress', @@ -246,7 +205,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; - delete requestBody.stix.external_references; + requestBody = cloneForCreate(requestBody); const res = await request(app) .post(endpoint) @@ -303,16 +262,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; - // Need to add kill_chain_phases for validation to pass for reviewed techniques - requestBody.stix.kill_chain_phases = [ - { - kill_chain_name: 'mitre-attack', - phase_name: 'initial-access', - }, - ]; - - // Unset/remove the external_references field because that is handled by the rest-api - delete requestBody.stix.external_references; + requestBody = cloneForCreate(requestBody); const res = await request(app) .post(endpoint) @@ -403,10 +353,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; - delete createBody.stix.external_references; - - // Filters the properties of an object, returning a new object containing only those entries whose values pass the validity check. - createBody = filterObject(createBody); + createBody = cloneForCreate(createBody); const createRes = await request(app) .post(endpoint) @@ -434,14 +381,12 @@ describe('ADM Validation Middleware', function () { }, }; + updateBody = cloneForCreate(updateBody); + // Remove server-managed field (server adds this automatically) delete updateBody.stix.x_mitre_attack_spec_version; - // Unset/remove the external_references field because that is handled by the rest-api - delete updateBody.stix.external_references; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure - // Filters the properties of an object, returning a new object containing only those entries whose values pass the validity check. - updateBody = filterObject(updateBody); const res = await request(app) .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) @@ -474,18 +419,16 @@ describe('ADM Validation Middleware', function () { }, }; + updateBody = cloneForCreate(updateBody); + // Remove optional fields to test partial validation delete updateBody.stix.description; delete updateBody.stix.x_mitre_platforms; - // Unset/remove the external_references field because that is handled by the rest-api - delete updateBody.stix.external_references; // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure - - // Filters the properties of an object, returning a new object containing only those entries whose values pass the validity check. - updateBody = filterObject(updateBody); + const res = await request(app) .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) @@ -541,8 +484,7 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; - // Unset/remove the external_references field because that is handled by the rest-api - delete createBody.stix.external_references; + createBody = cloneForCreate(createBody); const createRes = await request(app) .post(endpoint) @@ -567,18 +509,10 @@ describe('ADM Validation Middleware', function () { }, }; - // Need to add kill_chain_phases for validation to pass for reviewed techniques - updateBody.stix.kill_chain_phases = [ - { - kill_chain_name: 'mitre-attack', - phase_name: 'initial-access', - }, - ]; + updateBody = cloneForCreate(updateBody); // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; - // Unset/remove the external_references field because that is handled by the rest-api - delete updateBody.stix.external_references; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure const res = await request(app) diff --git a/app/tests/shared/clone-for-create.js b/app/tests/shared/clone-for-create.js index b16d297c..900a0a7e 100644 --- a/app/tests/shared/clone-for-create.js +++ b/app/tests/shared/clone-for-create.js @@ -44,6 +44,10 @@ function cloneForCreate(attackObject) { cloned.stix.external_references = cloned.stix.external_references.filter( (ref) => !config.attackSourceNames.includes(ref.source_name), ); + // If the list is now empty, remove the field + if (cloned.stix.external_references.length === 0) { + delete cloned.stix.external_references; + } } return cloned; From 86df1e197c9ea5e091389c8135c5c72ff4741109 Mon Sep 17 00:00:00 2001 From: adpare Date: Sat, 6 Dec 2025 18:52:59 -0500 Subject: [PATCH 143/370] chore: fix identation --- app/models/subschemas/attack-pattern.js | 2 +- app/tests/middleware/adm-validation-middleware.spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/subschemas/attack-pattern.js b/app/models/subschemas/attack-pattern.js index ee64a7cb..169a74fa 100644 --- a/app/models/subschemas/attack-pattern.js +++ b/app/models/subschemas/attack-pattern.js @@ -9,7 +9,7 @@ module.exports.attackPattern = { description: String, kill_chain_phases: { type: [stixCore.killChainPhaseSchema], - default: undefined + default: undefined, }, // ATT&CK custom STIX properties diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 03651deb..1bc80909 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -11,7 +11,7 @@ logger.level = 'debug'; const uuid = require('uuid'); const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/dist/generator'); -const { cloneForCreate } = require('../shared/clone-for-create') +const { cloneForCreate } = require('../shared/clone-for-create'); /** * Smoke tests for ATT&CK Data Model (ADM) validation middleware. @@ -428,7 +428,7 @@ describe('ADM Validation Middleware', function () { // Remove server-managed field delete updateBody.stix.x_mitre_attack_spec_version; // Note: We keep id, created, modified because ADM schemas validate the full STIX structure - + const res = await request(app) .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) From e75d3adaa148c4af21de3fdcc58421e1e28b417a Mon Sep 17 00:00:00 2001 From: adpare Date: Sat, 6 Dec 2025 22:52:41 -0500 Subject: [PATCH 144/370] chore: update package-lock --- package-lock.json | 6298 ++++++++++++++++++++++++++------------------- 1 file changed, 3692 insertions(+), 2606 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f0f2172..085d0a5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,9 +109,9 @@ "license": "MIT" }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-13.0.1.tgz", - "integrity": "sha512-91uy6MGWqu7CjcV7tLPMuYh/Wj/RNPBXquSdEaCEpj2H/cFy0Yu+t1EdxExSyaryl1ykhDo30plq9tIm/HVZnw==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-13.0.5.tgz", + "integrity": "sha512-xfh4xVJD62gG6spIc7lwxoWT+l16nZu1ELyU8FkjaP/oD2yP09EvLAU6KhtudN9aML2Khhs9pY6Slr7KGTES3w==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.15", @@ -125,28 +125,43 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", "engines": { @@ -169,159 +184,10 @@ "semver": "^7.3.2" } }, - "node_modules/@codedependant/semantic-release-docker/node_modules/@semantic-release/error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", - "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@codedependant/semantic-release-docker/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@colors/colors": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "license": "MIT", "optional": true, "engines": { @@ -350,40 +216,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@commitlint/cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@commitlint/config-conventional": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", @@ -454,19 +286,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/format/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/is-ignored": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", @@ -519,19 +338,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@commitlint/message": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", @@ -557,61 +363,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/parse/node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@commitlint/parse/node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@commitlint/parse/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@commitlint/parse/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/@commitlint/read": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", @@ -647,16 +398,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@commitlint/rules": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", @@ -710,44 +451,53 @@ "node": ">=v18" } }, - "node_modules/@commitlint/types/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@dabh/diagnostics": { - "version": "2.0.2", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", "license": "MIT", "dependencies": { - "colorspace": "1.1.x", + "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -755,13 +505,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -769,45 +519,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -818,9 +546,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -830,7 +558,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -858,24 +586,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -883,17 +593,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { @@ -904,9 +607,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -914,13 +617,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -929,6 +632,8 @@ }, "node_modules/@ewoudenberg/difflib": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ewoudenberg/difflib/-/difflib-0.1.0.tgz", + "integrity": "sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==", "dev": true, "dependencies": { "heap": ">= 0.2.0" @@ -946,6 +651,8 @@ }, "node_modules/@humanfs/core": { "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -953,31 +660,23 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1004,6 +703,8 @@ }, "node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "license": "ISC", "dependencies": { @@ -1018,35 +719,17 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1061,84 +744,117 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "jest-get-type": "^29.6.3" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "color-convert": "^2.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" } }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1146,27 +862,33 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.1.tgz", - "integrity": "sha512-0b5Q+67SzsfHjAWYVCL8AQ5qlNa+poLDhfWt4n7vYQKQwyqJr6BEU7x+mcQpj4V/+B0qmJiBWx7uBeb0h778rQ==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.2.tgz", + "integrity": "sha512-4MzEkYcXqe5rWnlj6Iy/aE4+wXkAv3q7rJUOHairl24HtiTqDHxVavz96KJBNNpRyIcEXTlCm93AQpVp/uR4MA==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", @@ -1188,7 +910,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", + "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -1206,205 +930,183 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/core": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz", - "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.1.2", - "@octokit/request": "^9.2.1", - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", - "before-after-hook": "^3.0.2", + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/endpoint": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz", - "integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.6.2", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/graphql": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz", - "integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^9.2.2", - "@octokit/types": "^13.8.0", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.4.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.3.tgz", - "integrity": "sha512-tBXaAbXkqVJlRoA/zQVe9mUdb8rScmivqtpv3ovsC5xhje/a+NOCivs7eUhWBwCApJVsR4G5HMeaLbq7PxqZGA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.7.0" + "@octokit/types": "^15.0.1" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { "@octokit/core": ">=6" } }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", + "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^26.0.0" + } + }, "node_modules/@octokit/plugin-retry": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.4.tgz", - "integrity": "sha512-7AIP4p9TttKN7ctygG4BtR7rrB0anZqoU9ThXFk8nETqIfvgPUANTSYHqWYknK7W3isw59LpZeLI8pcEwiJdRg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", + "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": ">=6" + "@octokit/core": ">=7" } }, "node_modules/@octokit/plugin-throttling": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.4.0.tgz", - "integrity": "sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.7.0", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "^6.1.3" + "@octokit/core": "^7.0.0" } }, "node_modules/@octokit/request": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz", - "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^10.1.3", - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", - "fast-content-type-parse": "^2.0.0", + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/request-error": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz", - "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.6.2" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^27.0.0" } }, "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "license": "MIT", "dependencies": { "@noble/hashes": "^1.1.5" @@ -1412,6 +1114,8 @@ }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, @@ -1420,9 +1124,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -1479,6 +1183,8 @@ }, "node_modules/@scarf/scarf": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", "hasInstallScript": true, "license": "Apache-2.0" }, @@ -1512,63 +1218,80 @@ "semantic-release": ">=20.1.0" } }, - "node_modules/@semantic-release/commit-analyzer/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=18" + } + }, + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "meow": "^13.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@semantic-release/commit-analyzer/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@semantic-release/commit-analyzer/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=14.17" } }, "node_modules/@semantic-release/github": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.1.tgz", - "integrity": "sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", + "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^6.0.0", - "@octokit/plugin-paginate-rest": "^11.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^9.0.0", + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", - "globby": "^14.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", "url-join": "^5.0.0" }, "engines": { @@ -1578,118 +1301,222 @@ "semantic-release": ">=24.1.0" } }, - "node_modules/@semantic-release/github/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14" + "node": ">=18" } }, - "node_modules/@semantic-release/github/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@semantic-release/npm": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", + "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.9.3", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=20.8.1" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/github/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">= 14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/github/node_modules/mime": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz", - "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==", + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "funding": [ - "https://github.com/sponsors/broofa" - ], "license": "MIT", - "bin": { - "mime": "bin/cli.js" - }, "engines": { - "node": ">=16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/github/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@semantic-release/npm": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", - "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", - "dependencies": { - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "execa": "^9.0.0", - "fs-extra": "^11.0.0", - "lodash-es": "^4.17.21", - "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", - "npm": "^10.5.0", - "rc": "^1.2.8", - "read-pkg": "^9.0.0", - "registry-auth-token": "^5.0.0", - "semver": "^7.1.2", - "tempy": "^3.0.0" - }, "engines": { - "node": ">=20.8.1" + "node": ">=18" }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=14.14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@semantic-release/release-notes-generator": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.3.tgz", - "integrity": "sha512-XxAZRPWGwO5JwJtS83bRdoIhCiYIx8Vhr+u231pQAsdFIAbm19rSVJLdnBN+Avvk7CKvNQE/nJ4y7uqKH6WTiw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", + "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", "dev": true, "license": "MIT", "dependencies": { @@ -1711,22 +1538,33 @@ "semantic-release": ">=20.1.0" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=18" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "meow": "^13.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { @@ -1742,15 +1580,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/@sinclair/typebox": { "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true, "license": "MIT" }, @@ -1801,14 +1647,13 @@ } }, "node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", "type-detect": "^4.1.0" } }, @@ -1822,8 +1667,20 @@ "node": ">=4" } }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, "node_modules/@types/body-parser": { - "version": "1.19.1", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -1831,16 +1688,18 @@ } }, "node_modules/@types/connect": { - "version": "3.4.35", + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/conventional-commits-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", - "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", + "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", "dev": true, "license": "MIT", "dependencies": { @@ -1848,22 +1707,27 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.0", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^2" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -1872,23 +1736,23 @@ "@types/send": "*" } }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", "dependencies": { @@ -1897,6 +1761,8 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1905,30 +1771,49 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.7", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "license": "MIT", "dependencies": { + "@types/ms": "*", "@types/node": "*" } }, "node_modules/@types/mime": { - "version": "1.3.2", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, "node_modules/@types/multer": { - "version": "1.4.12", + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/node": { - "version": "14.11.8", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", - "peer": true + "dependencies": { + "undici-types": "~7.16.0" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -1938,51 +1823,69 @@ "license": "MIT" }, "node_modules/@types/qs": { - "version": "6.9.7", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "license": "MIT" }, "node_modules/@types/range-parser": { - "version": "1.2.4", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, "node_modules/@types/send": { - "version": "0.17.4", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.13.10", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "license": "MIT", "dependencies": { - "@types/mime": "^1", + "@types/http-errors": "*", "@types/node": "*" } }, "node_modules/@types/stack-utils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, "license": "MIT" }, "node_modules/@types/triple-beam": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", "license": "MIT" }, "node_modules/@types/whatwg-url": { - "version": "11.0.5", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", "license": "MIT", + "peer": true, "dependencies": { "@types/webidl-conversions": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.33", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, "license": "MIT", "dependencies": { @@ -1991,6 +1894,8 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, "license": "MIT" }, @@ -2007,13 +1912,21 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2031,6 +1944,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", @@ -2053,7 +1976,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2065,9 +1987,24 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ajv-formats": { - "version": "2.1.1", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -2082,9 +2019,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2098,14 +2035,22 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -2130,6 +2075,8 @@ }, "node_modules/argparse": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/argv-formatter": { @@ -2160,10 +2107,14 @@ }, "node_modules/async": { "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, "node_modules/async-await-retry": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/async-await-retry/-/async-await-retry-2.1.0.tgz", + "integrity": "sha512-eP0cVR8SfTussawaGL1edKe6aQJiVo2A8+TFBhpg3GKMHcn3FvAT/CfdaPtXdbsLsca/L7qwhi7e8D7SDFnoNQ==", "license": "MIT", "bin": { "async-await-retry": "index.js" @@ -2184,12 +2135,14 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/axios": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", - "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2198,27 +2151,46 @@ } }, "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } }, "node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, "license": "Apache-2.0", - "optional": true + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } }, "node_modules/basic-auth": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" @@ -2227,10 +2199,16 @@ "node": ">= 0.8" } }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, "license": "Apache-2.0" }, @@ -2253,42 +2231,9 @@ "engines": { "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bottleneck": { @@ -2299,9 +2244,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2311,6 +2256,8 @@ }, "node_modules/braces": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -2322,16 +2269,19 @@ }, "node_modules/browser-stdout": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true, "license": "ISC" }, "node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", + "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==", "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=16.20.1" + "node": ">=20.19.0" } }, "node_modules/buffer-crc32": { @@ -2346,6 +2296,8 @@ }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, "node_modules/buffer-from": { @@ -2367,6 +2319,8 @@ }, "node_modules/bytes": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2374,6 +2328,8 @@ }, "node_modules/c8": { "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, "license": "ISC", "dependencies": { @@ -2404,21 +2360,10 @@ } } }, - "node_modules/c8/node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/c8/node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -2434,6 +2379,8 @@ }, "node_modules/c8/node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -2448,6 +2395,8 @@ }, "node_modules/c8/node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2462,6 +2411,8 @@ }, "node_modules/c8/node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -2474,21 +2425,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/c8/node_modules/yargs": { - "version": "17.7.2", + "node_modules/c8/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/c8/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/call-bind-apply-helpers": { @@ -2522,6 +2479,8 @@ }, "node_modules/callsites": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -2530,6 +2489,8 @@ }, "node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -2540,22 +2501,125 @@ } }, "node_modules/chalk": { - "version": "4.1.0", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clean-stack/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/chalk/node_modules/ansi-styles": { + "node_modules/cli-highlight/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2568,8 +2632,39 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/chalk/node_modules/color-convert": { + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2579,186 +2674,256 @@ "node": ">=7.0.0" } }, - "node_modules/chalk/node_modules/color-name": { + "node_modules/cli-highlight/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">=10" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/ci-info": { - "version": "3.9.0", + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/clean-stack": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", - "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "5.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/clean-stack/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "license": "ISC", "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" + "node": ">=7.0.0" } }, - "node_modules/cli-highlight/node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { - "string-width": "^4.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "node": ">=8" } }, - "node_modules/cliui": { - "version": "7.0.4", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/clone": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/color": { - "version": "3.0.0", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", "license": "MIT", "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" } }, "node_modules/color-convert": { - "version": "1.9.3", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" } }, "node_modules/color-name": { - "version": "1.1.3", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } }, "node_modules/color-string": { - "version": "1.5.5", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", "license": "MIT", "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/colors": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.1.90" } }, - "node_modules/colorspace": { - "version": "1.1.2", - "license": "MIT", - "dependencies": { - "color": "3.0.x", - "text-hex": "1.0.x" - } - }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -2768,7 +2933,9 @@ } }, "node_modules/commander": { - "version": "9.3.0", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", "engines": { "node": "^12.20.0 || >=14" @@ -2793,11 +2960,18 @@ } }, "node_modules/component-emitter": { - "version": "1.3.0", - "license": "MIT" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/compressible": { "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -2824,29 +2998,19 @@ "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/concat-map": { @@ -2871,20 +3035,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -2896,6 +3046,13 @@ "proto-list": "~1.2.1" } }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2908,44 +3065,26 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/content-type": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/conventional-changelog-angular": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", "dev": true, "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=16" } }, "node_modules/conventional-changelog-conventionalcommits": { @@ -2962,9 +3101,9 @@ } }, "node_modules/conventional-changelog-writer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", - "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", + "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -2980,6 +3119,19 @@ "node": ">=18" } }, + "node_modules/conventional-changelog-writer/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/conventional-commits-filter": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", @@ -2991,19 +3143,22 @@ } }, "node_modules/conventional-commits-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", - "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", "dev": true, "license": "MIT", "dependencies": { - "meow": "^13.0.0" + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" }, "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "conventional-commits-parser": "cli.mjs" }, "engines": { - "node": ">=18" + "node": ">=16" } }, "node_modules/convert-hrtime": { @@ -3020,15 +3175,16 @@ } }, "node_modules/convert-source-map": { - "version": "1.7.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" - } + "license": "MIT" }, "node_modules/convict": { "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", "license": "Apache-2.0", "dependencies": { "lodash.clonedeep": "^4.5.0", @@ -3040,36 +3196,50 @@ }, "node_modules/convict/node_modules/yargs-parser": { "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/cookie": { - "version": "1.0.2", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, "node_modules/cookiejar": { "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "license": "MIT" }, "node_modules/core-util-is": { - "version": "1.0.2", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -3085,7 +3255,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -3108,13 +3277,13 @@ } }, "node_modules/cosmiconfig-typescript-loader": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz", - "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", "dev": true, "license": "MIT", "dependencies": { - "jiti": "^2.4.1" + "jiti": "^2.6.1" }, "engines": { "node": ">=v18" @@ -3139,6 +3308,8 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -3193,21 +3364,49 @@ } }, "node_modules/date-fns": { - "version": "2.28.0", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, "engines": { "node": ">=0.11" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/deep-extend": { @@ -3222,11 +3421,15 @@ }, "node_modules/deep-is": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3234,6 +3437,8 @@ }, "node_modules/depd": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3271,6 +3476,8 @@ }, "node_modules/diff-sequences": { "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { @@ -3305,6 +3512,8 @@ }, "node_modules/dreamopt": { "version": "0.8.0", + "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.8.0.tgz", + "integrity": "sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==", "dev": true, "dependencies": { "wordwrap": ">=0.0.2" @@ -3337,13 +3546,50 @@ "readable-stream": "^2.0.2" } }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -3351,10 +3597,14 @@ }, "node_modules/ee-first": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/emojilib": { @@ -3366,6 +3616,8 @@ }, "node_modules/enabled": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, "node_modules/encodeurl": { @@ -3388,9 +3640,9 @@ } }, "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3401,9 +3653,9 @@ } }, "node_modules/env-ci": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", - "integrity": "sha512-Z8dnwSDbV1XYM9SBF2J0GcNVvmfmfh3a49qddGIROhBoVro6MZVTji15z/sJbQ2ko2ei8n988EU1wzoLU/tF+g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", "dev": true, "license": "MIT", "dependencies": { @@ -3474,6 +3726,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/env-ci/node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -3490,6 +3755,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/env-ci/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -3503,6 +3784,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/env-ci/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/env-ci/node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -3540,22 +3834,15 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -3567,6 +3854,8 @@ }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -3586,7 +3875,7 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { @@ -3600,7 +3889,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -3614,42 +3905,45 @@ "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "2.0.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3684,12 +3978,11 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3701,9 +3994,9 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz", - "integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "license": "MIT", "dependencies": { @@ -3732,9 +4025,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3749,11 +4042,13 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3776,46 +4071,63 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/debug": { - "version": "4.3.4", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=7.0.0" } }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -3829,17 +4141,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3849,6 +4150,8 @@ }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -3861,13 +4164,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, "node_modules/eslint/node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3882,6 +4182,8 @@ }, "node_modules/eslint/node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -3894,30 +4196,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3926,7 +4238,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3951,6 +4265,8 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3959,6 +4275,8 @@ }, "node_modules/esutils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3974,78 +4292,44 @@ "node": ">= 0.6" } }, - "node_modules/execa": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", - "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.3", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.0", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bare-events": "^2.7.0" } }, - "node_modules/execa/node_modules/is-plain-obj": { + "node_modules/execa": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/expect": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { @@ -4060,40 +4344,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -4107,13 +4390,13 @@ } }, "node_modules/express-openapi-validator": { - "version": "5.5.7", - "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.5.7.tgz", - "integrity": "sha512-QcV941hD3GHnCOg72y6kgqA7XsDkmZOGf7tvoIw9IyUlIRvJv/XDpYFnK7rOggt+ZUaM2P/9wsZCmdMXpr7z2A==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.0.tgz", + "integrity": "sha512-gNaMgDb1cAT8QKcuh9WrED9p3mqi/V7yocNrvnE1fOz7e8p8JkbYaTUcOB4VsZKerz/X+Sey7ptTGF5FwsXh8Q==", "license": "MIT", "dependencies": { - "@apidevtools/json-schema-ref-parser": "^12.0.1", - "@types/multer": "^1.4.12", + "@apidevtools/json-schema-ref-parser": "^14.0.3", + "@types/multer": "^1.4.13", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", @@ -4122,7 +4405,7 @@ "lodash.clonedeep": "^4.5.0", "lodash.get": "^4.4.2", "media-typer": "^1.1.0", - "multer": "^2.0.1", + "multer": "^2.0.2", "ono": "^7.1.3", "path-to-regexp": "^8.2.0", "qs": "^6.14.0" @@ -4132,49 +4415,31 @@ } }, "node_modules/express-openapi-validator/node_modules/@apidevtools/json-schema-ref-parser": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-12.0.2.tgz", - "integrity": "sha512-SoZWqQz4YMKdw4kEMfG5w6QAy+rntjsoAT1FtvZAnVEnCR4uy9YSuDBNoVAFHgzSz0dJbISLLCSrGR2Zd7bcvA==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", + "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", "license": "MIT", "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" }, "engines": { - "node": ">= 16" + "node": ">= 20" }, "funding": { "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/express-openapi-validator/node_modules/ajv-draft-04": { - "version": "1.0.0", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "peerDependencies": { + "@types/json-schema": "^7.0.15" } }, - "node_modules/express-openapi-validator/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "node_modules/express-openapi-validator/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session": { @@ -4198,51 +4463,46 @@ }, "node_modules/express-session/node_modules/cookie": { "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/express-session/node_modules/cookie-signature": { - "version": "1.0.7", - "license": "MIT" + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/express-session/node_modules/safe-buffer": { - "version": "5.2.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, "node_modules/express/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -4250,12 +4510,23 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.7.1", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/express/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4277,72 +4548,27 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/express/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/express/node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4357,9 +4583,9 @@ } }, "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", "dev": true, "funding": [ { @@ -4375,10 +4601,14 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, "license": "Apache-2.0" }, @@ -4389,23 +4619,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4415,29 +4628,37 @@ }, "node_modules/fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.3", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "BSD-3-Clause" }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fecha": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, "node_modules/figures": { @@ -4471,6 +4692,8 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4482,6 +4705,8 @@ }, "node_modules/fill-range": { "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -4492,35 +4717,38 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" + "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", @@ -4584,32 +4812,9 @@ } }, "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/find-up/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, "license": "MIT", "engines": { @@ -4638,6 +4843,8 @@ }, "node_modules/flat": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "license": "BSD-3-Clause", "bin": { @@ -4646,6 +4853,8 @@ }, "node_modules/flat-cache": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -4657,12 +4866,16 @@ } }, "node_modules/flatted": { - "version": "3.3.2", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, "node_modules/fn-args": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", + "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==", "license": "MIT", "engines": { "node": ">=8" @@ -4670,12 +4883,14 @@ }, "node_modules/fn.name": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -4693,11 +4908,13 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -4707,10 +4924,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -4742,6 +4972,8 @@ }, "node_modules/forwarded": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -4767,8 +4999,43 @@ "readable-stream": "^2.0.0" } }, + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/from2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/fs-extra": { "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -4781,6 +5048,8 @@ }, "node_modules/function-bind": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4801,6 +5070,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -4845,13 +5116,16 @@ } }, "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4872,6 +5146,16 @@ "traverse": "0.6.8" } }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "~2.0.0" + } + }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -4890,31 +5174,10 @@ "node": ">=16" } }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/git-raw-commits/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/glob": { - "version": "10.4.5", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -4933,18 +5196,22 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4953,6 +5220,8 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -4981,16 +5250,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -5004,76 +5263,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", - "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/globby/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -5088,6 +5277,8 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, "node_modules/handlebars": { @@ -5114,6 +5305,8 @@ }, "node_modules/has-flag": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -5125,9 +5318,6 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, "engines": { "node": ">= 0.4" }, @@ -5137,7 +5327,7 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { @@ -5152,6 +5342,8 @@ }, "node_modules/hasown": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5162,6 +5354,8 @@ }, "node_modules/he": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", "bin": { @@ -5170,6 +5364,8 @@ }, "node_modules/heap": { "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "dev": true, "license": "MIT" }, @@ -5193,22 +5389,22 @@ } }, "node_modules/hook-std": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", - "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", + "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/hosted-git-info": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz", - "integrity": "sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, "license": "ISC", "dependencies": { @@ -5227,21 +5423,29 @@ }, "node_modules/html-escaper": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-proxy-agent": { @@ -5258,55 +5462,34 @@ "node": ">= 14" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "license": "MIT" }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-status-codes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", - "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", - "license": "MIT" - }, "node_modules/human-signals": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", - "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18.18.0" + "node": ">=8.12.0" } }, "node_modules/husky": { @@ -5352,7 +5535,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5366,6 +5551,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/import-from-esm": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", @@ -5380,35 +5575,10 @@ "node": ">=18.20" } }, - "node_modules/import-from-esm/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/import-from-esm/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", "funding": { @@ -5418,6 +5588,8 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { @@ -5438,9 +5610,9 @@ } }, "node_modules/index-to-position": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", - "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", "dev": true, "license": "MIT", "engines": { @@ -5452,14 +5624,19 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/into-stream": { "version": "7.0.0", @@ -5480,17 +5657,24 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/is-arrayish": { - "version": "0.3.2", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, "node_modules/is-extglob": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -5499,6 +5683,8 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -5506,6 +5692,8 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -5517,6 +5705,8 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -5533,8 +5723,20 @@ "node": ">=8" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, "license": "MIT", "engines": { @@ -5542,10 +5744,15 @@ } }, "node_modules/is-stream": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-text-path": { @@ -5563,6 +5770,8 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", "engines": { @@ -5574,11 +5783,15 @@ }, "node_modules/isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, @@ -5601,6 +5814,8 @@ }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5609,6 +5824,8 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5621,7 +5838,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5634,6 +5853,8 @@ }, "node_modules/jackspeak": { "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -5642,27 +5863,108 @@ "funding": { "url": "https://github.com/sponsors/isaacs" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/java-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff": { + "node_modules/jest-matcher-utils": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" }, @@ -5670,30 +5972,63 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-message-util": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { @@ -5711,8 +6046,63 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-util": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { @@ -5727,10 +6117,63 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { @@ -5739,6 +6182,8 @@ }, "node_modules/jose": { "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -5746,11 +6191,15 @@ }, "node_modules/js-tokens": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -5761,11 +6210,15 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-diff": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-1.0.6.tgz", + "integrity": "sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==", "dev": true, "license": "MIT", "dependencies": { @@ -5802,11 +6255,15 @@ }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/jsonfile": { - "version": "6.1.0", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -5843,10 +6300,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -5862,15 +6321,13 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, "node_modules/jwa": { - "version": "1.4.1", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -5893,44 +6350,64 @@ } }, "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.21", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, - "node_modules/jwks-rsa/node_modules/debug": { - "version": "4.3.4", + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", "license": "MIT", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/jwks-rsa/node_modules/ms": { - "version": "2.1.2", - "license": "MIT" + "node_modules/jwks-rsa/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } }, "node_modules/jws": { - "version": "3.2.2", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "node_modules/jwt-decode": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", "license": "MIT", "engines": { "node": ">=18" @@ -5947,6 +6424,8 @@ }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -5955,10 +6434,14 @@ }, "node_modules/kuler": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5970,7 +6453,9 @@ } }, "node_modules/limiter": { - "version": "1.1.5" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, "node_modules/lines-and-columns": { "version": "1.2.4", @@ -6027,6 +6512,8 @@ }, "node_modules/lodash": { "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, "node_modules/lodash-es": { @@ -6052,6 +6539,8 @@ }, "node_modules/lodash.clonedeep": { "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "license": "MIT" }, "node_modules/lodash.escaperegexp": { @@ -6075,14 +6564,21 @@ }, "node_modules/lodash.get": { "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, "node_modules/lodash.isempty": { @@ -6093,18 +6589,26 @@ }, "node_modules/lodash.isinteger": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, "node_modules/lodash.kebabcase": { @@ -6122,6 +6626,8 @@ }, "node_modules/lodash.merge": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, @@ -6134,6 +6640,8 @@ }, "node_modules/lodash.once": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, "node_modules/lodash.snakecase": { @@ -6179,6 +6687,8 @@ }, "node_modules/log-symbols": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", "dependencies": { @@ -6192,8 +6702,63 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/logform": { "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", @@ -6209,23 +6774,23 @@ }, "node_modules/logform/node_modules/@colors/colors": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", "engines": { "node": ">=0.1.90" } }, - "node_modules/logform/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, "node_modules/long-timeout": { "version": "0.1.1", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/long-timeout/-/long-timeout-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", "license": "MIT" }, "node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -6236,6 +6801,8 @@ }, "node_modules/lru-memoizer": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", "license": "MIT", "dependencies": { "lodash.clonedeep": "^4.5.0", @@ -6251,8 +6818,28 @@ "node": ">=12" } }, + "node_modules/make-asynchronous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", + "integrity": "sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "1.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-dir": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -6271,7 +6858,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -6295,36 +6881,10 @@ "supports-hyperlinks": "^3.1.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "marked": ">=1 <16" - } - }, - "node_modules/marked-terminal/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "marked": ">=1 <16" } }, "node_modules/math-intrinsics": { @@ -6347,16 +6907,18 @@ }, "node_modules/memory-pager": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "license": "MIT" }, "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=16.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6378,18 +6940,10 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/methods": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6397,6 +6951,8 @@ }, "node_modules/micromatch": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -6437,19 +6993,25 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], "license": "MIT", "bin": { - "mime": "cli.js" + "mime": "bin/cli.js" }, "engines": { - "node": ">=4" + "node": ">=16" } }, "node_modules/mime-db": { - "version": "1.52.0", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -6457,6 +7019,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -6465,17 +7029,23 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/minimatch": { @@ -6502,6 +7072,8 @@ }, "node_modules/minipass": { "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { @@ -6521,9 +7093,9 @@ } }, "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, "license": "MIT", "dependencies": { @@ -6535,6 +7107,7 @@ "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", @@ -6543,7 +7116,7 @@ "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -6557,59 +7130,19 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/mocha/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mocha/node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { @@ -6625,6 +7158,8 @@ }, "node_modules/mocha/node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -6653,13 +7188,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/mocha/node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6674,6 +7206,8 @@ }, "node_modules/mocha/node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -6686,8 +7220,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6700,47 +7246,41 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mongodb": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", - "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", + "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.3", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.0.0", + "mongodb-connection-string-url": "^7.0.0" }, "engines": { - "node": ">=16.20.1" + "node": ">=20.19.0" }, "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" }, "peerDependenciesMeta": { "@aws-sdk/credential-providers": { @@ -6767,110 +7307,145 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.1", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", + "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" } }, "node_modules/mongodb-memory-server": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.1.4.tgz", - "integrity": "sha512-+oKQ/kc3CX+816oPFRtaF0CN4vNcGKNjpOQe4bHo/21A3pMD+lC7Xz1EX5HP7siCX4iCpVchDMmCOFXVQSGkUg==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.1.tgz", + "integrity": "sha512-XpCyV1e7QQ1lW28rgtXP4ZlX8ZfD/8z1ZGNxz2y3JrosLgDrNnYWvPjlgFj3JjboYUtlh1jF2Ez/rwsQA6cl0w==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "mongodb-memory-server-core": "10.1.4", - "tslib": "^2.7.0" + "mongodb-memory-server-core": "10.4.1", + "tslib": "^2.8.1" }, "engines": { "node": ">=16.20.1" } }, "node_modules/mongodb-memory-server-core": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.1.4.tgz", - "integrity": "sha512-o8fgY7ZalEd8pGps43fFPr/hkQu1L8i6HFEGbsTfA2zDOW0TopgpswaBCqDr0qD7ptibyPfB5DmC+UlIxbThzA==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.1.tgz", + "integrity": "sha512-YJdrEyF9hk64nfeoVDMP6IfTzK+gLZhrQqYyP6JJMsqo2LK5eF7JRZ4YPQDmt1re/JhItpiU+ypiZbIG1OsW5Q==", "dev": true, "license": "MIT", "dependencies": { "async-mutex": "^0.5.0", "camelcase": "^6.3.0", - "debug": "^4.3.7", + "debug": "^4.4.3", "find-cache-dir": "^3.3.2", - "follow-redirects": "^1.15.9", - "https-proxy-agent": "^7.0.5", + "follow-redirects": "^1.15.11", + "https-proxy-agent": "^7.0.6", "mongodb": "^6.9.0", "new-find-package-json": "^2.0.0", - "semver": "^7.6.3", + "semver": "^7.7.3", "tar-stream": "^3.1.7", - "tslib": "^2.7.0", - "yauzl": "^3.1.3" + "tslib": "^2.8.1", + "yauzl": "^3.2.0" }, "engines": { "node": ">=16.20.1" } }, - "node_modules/mongodb-memory-server-core/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", "dev": true, "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 14" + "node": ">=16.20.1" } }, - "node_modules/mongodb-memory-server-core/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", + "integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ms": "^2.1.3" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { - "node": ">=6.0" + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" }, "peerDependenciesMeta": { - "supports-color": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { "optional": true } } }, - "node_modules/mongodb-memory-server-core/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/mongodb-memory-server-core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/mongoose": { - "version": "8.15.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.1.tgz", - "integrity": "sha512-RhQ4DzmBi5BNGcS0w4u1vdMRIKcteXTCNzDt1j7XRcdWYBz1MjMjulBhPaeC5jBCHOD1yinuOFTTSOWLLGexWw==", + "version": "8.20.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.2.tgz", + "integrity": "sha512-U0TPupnqBOAI3p9H9qdShX8/nJUBylliRcHFKuhbewEkM7Y0qc9BbrQR9h4q6+1easoZqej7cq2Ee36AZ0gMzQ==", "license": "MIT", "dependencies": { - "bson": "^6.10.3", + "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.16.0", + "mongodb": "~6.20.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -6884,9 +7459,79 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } }, "node_modules/morgan": { "version": "1.10.1", @@ -6904,8 +7549,37 @@ "node": ">= 0.8.0" } }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mpath": { "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", "license": "MIT", "engines": { "node": ">=4.0.0" @@ -6923,37 +7597,16 @@ "node": ">=14.0.0" } }, - "node_modules/mquery/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mquery/node_modules/ms": { + "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, "node_modules/multer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz", - "integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -7003,9 +7656,9 @@ } }, "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "funding": [ { "type": "github", @@ -7022,13 +7675,15 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7061,33 +7716,10 @@ "node": ">=12.22.0" } }, - "node_modules/new-find-package-json/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/new-find-package-json/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/node-cache": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", "license": "MIT", "dependencies": { "clone": "2.x" @@ -7126,6 +7758,15 @@ "node": ">=6" } }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/normalize-package-data": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", @@ -7162,9 +7803,9 @@ "license": "ISC" }, "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "dev": true, "license": "MIT", "engines": { @@ -7175,9 +7816,9 @@ } }, "node_modules/npm": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", - "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -7259,37 +7900,37 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/config": "^9.0.0", "@npmcli/fs": "^4.0.0", "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.1.0", + "@npmcli/package-json": "^6.2.0", "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "@sigstore/tuf": "^3.0.0", - "abbrev": "^3.0.0", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", "archy": "~1.0.0", "cacache": "^19.0.1", - "chalk": "^5.3.0", - "ci-info": "^4.1.0", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", "glob": "^10.4.5", "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.0.2", + "hosted-git-info": "^8.1.0", "ini": "^5.0.0", "init-package-json": "^7.0.2", - "is-cidr": "^5.1.0", + "is-cidr": "^5.1.1", "json-parse-even-better-errors": "^4.0.0", "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.0", - "libnpmexec": "^9.0.0", - "libnpmfund": "^6.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", "libnpmhook": "^11.0.0", "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.0", + "libnpmpack": "^8.0.1", "libnpmpublish": "^10.0.1", "libnpmsearch": "^8.0.0", "libnpmteam": "^7.0.0", @@ -7299,23 +7940,23 @@ "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^11.0.0", - "nopt": "^8.0.0", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", "normalize-package-data": "^7.0.0", "npm-audit-report": "^6.0.0", "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.0", + "npm-package-arg": "^12.0.2", "npm-pick-manifest": "^10.0.0", "npm-profile": "^11.0.1", "npm-registry-fetch": "^18.0.2", "npm-user-validate": "^3.0.0", - "p-map": "^4.0.0", + "p-map": "^7.0.3", "pacote": "^19.0.1", "parse-conflict-json": "^4.0.0", "proc-log": "^5.0.0", "qrcode-terminal": "^0.12.0", - "read": "^4.0.0", - "semver": "^7.6.3", + "read": "^4.1.0", + "semver": "^7.7.2", "spdx-expression-parse": "^4.0.0", "ssri": "^12.0.0", "supports-color": "^9.4.0", @@ -7323,7 +7964,7 @@ "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.0", + "validate-npm-package-name": "^6.0.1", "which": "^5.0.0", "write-file-atomic": "^6.0.0" }, @@ -7336,33 +7977,16 @@ } }, "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" + "path-key": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/npm/node_modules/@isaacs/cliui": { @@ -7467,7 +8091,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.0", + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -7547,7 +8171,7 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.1", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -7557,7 +8181,6 @@ "lru-cache": "^10.0.1", "npm-pick-manifest": "^10.0.0", "proc-log": "^5.0.0", - "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^5.0.0" @@ -7663,7 +8286,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.1.0", + "version": "6.2.0", "dev": true, "inBundle": true, "license": "ISC", @@ -7672,9 +8295,9 @@ "glob": "^10.2.2", "hosted-git-info": "^8.0.0", "json-parse-even-better-errors": "^4.0.0", - "normalize-package-data": "^7.0.0", "proc-log": "^5.0.0", - "semver": "^7.5.3" + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -7693,19 +8316,19 @@ } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.0", + "version": "4.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.0.0", + "version": "3.2.2", "dev": true, "inBundle": true, "license": "ISC", @@ -7714,7 +8337,7 @@ } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.0.2", + "version": "9.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -7741,21 +8364,21 @@ } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", + "version": "0.4.3", "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.0.0", + "version": "3.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/protobuf-specs": "^0.4.1", "tuf-js": "^3.0.1" }, "engines": { @@ -7772,7 +8395,7 @@ } }, "node_modules/npm/node_modules/abbrev": { - "version": "3.0.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -7781,30 +8404,14 @@ } }, "node_modules/npm/node_modules/agent-base": { - "version": "7.1.1", + "version": "7.1.3", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -7873,7 +8480,7 @@ } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "MIT", @@ -7913,19 +8520,6 @@ "node": ">=18" } }, - "node_modules/npm/node_modules/cacache/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { "version": "3.0.1", "dev": true, @@ -7941,18 +8535,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/cacache/node_modules/p-map": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/cacache/node_modules/tar": { "version": "7.4.3", "dev": true, @@ -7980,7 +8562,7 @@ } }, "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", + "version": "5.4.1", "dev": true, "inBundle": true, "license": "MIT", @@ -8001,7 +8583,7 @@ } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.1.0", + "version": "4.2.0", "dev": true, "funding": [ { @@ -8016,7 +8598,7 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.1", + "version": "4.1.3", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -8027,15 +8609,6 @@ "node": ">=14" } }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/npm/node_modules/cli-columns": { "version": "4.0.0", "dev": true, @@ -8124,7 +8697,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.7", + "version": "4.4.1", "dev": true, "inBundle": true, "license": "MIT", @@ -8187,7 +8760,7 @@ "license": "MIT" }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "inBundle": true, "license": "Apache-2.0" @@ -8202,12 +8775,12 @@ } }, "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.0", + "version": "3.3.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -8256,7 +8829,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "8.0.2", + "version": "8.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -8268,7 +8841,7 @@ } }, "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", + "version": "4.2.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause" @@ -8287,12 +8860,12 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.5", + "version": "7.0.6", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -8333,15 +8906,6 @@ "node": ">=0.8.19" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/ini": { "version": "5.0.0", "dev": true, @@ -8395,7 +8959,7 @@ } }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.0", + "version": "5.1.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -8495,12 +9059,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.0", + "version": "7.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/installed-package-contents": "^3.0.0", "binary-extensions": "^2.3.0", "diff": "^5.1.0", @@ -8514,12 +9078,12 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.0", + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/run-script": "^9.0.1", "ci-info": "^4.0.0", "npm-package-arg": "^12.0.0", @@ -8535,12 +9099,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.0", + "version": "6.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0" + "@npmcli/arborist": "^8.0.1" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -8573,12 +9137,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.0", + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", + "@npmcli/arborist": "^8.0.1", "@npmcli/run-script": "^9.0.1", "npm-package-arg": "^12.0.0", "pacote": "^19.0.0" @@ -8721,7 +9285,7 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.0", + "version": "4.0.1", "dev": true, "inBundle": true, "license": "MIT", @@ -8737,19 +9301,6 @@ "encoding": "^0.1.13" } }, - "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", "dev": true, @@ -8823,28 +9374,15 @@ } }, "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "version": "3.0.2", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">= 18" } }, "node_modules/npm/node_modules/mkdirp": { @@ -8875,20 +9413,20 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "11.0.0", + "version": "11.2.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", + "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { @@ -8900,24 +9438,11 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 18" + "node": ">=18" } }, "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { @@ -8962,12 +9487,12 @@ } }, "node_modules/npm/node_modules/nopt": { - "version": "8.0.0", + "version": "8.1.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" @@ -8976,15 +9501,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/nopt/node_modules/abbrev": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/normalize-package-data": { "version": "7.0.0", "dev": true, @@ -9042,7 +9558,7 @@ } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.0", + "version": "12.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -9115,19 +9631,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/npm/node_modules/npm-user-validate": { "version": "3.0.0", "dev": true, @@ -9138,15 +9641,12 @@ } }, "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "version": "7.0.3", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9229,7 +9729,7 @@ } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.1.2", + "version": "7.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -9277,12 +9777,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", "dev": true, @@ -9317,7 +9811,7 @@ } }, "node_modules/npm/node_modules/read": { - "version": "4.0.0", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -9359,21 +9853,6 @@ "node": ">= 4" } }, - "node_modules/npm/node_modules/rimraf": { - "version": "5.0.10", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "dev": true, @@ -9382,7 +9861,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.2", "dev": true, "inBundle": true, "license": "ISC", @@ -9427,29 +9906,29 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", + "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^3.0.0", - "@sigstore/tuf": "^3.0.0", - "@sigstore/verify": "^2.0.0" + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -9465,15 +9944,15 @@ } }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", + "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^14.0.1", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, @@ -9482,14 +9961,14 @@ } }, "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.0.0", + "version": "2.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", + "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.4.1" }, "engines": { "node": "^18.17.0 || >=20.5.0" @@ -9506,7 +9985,7 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.3", + "version": "2.8.5", "dev": true, "inBundle": true, "license": "MIT", @@ -9520,12 +9999,12 @@ } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.4", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -9570,7 +10049,7 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.20", + "version": "3.0.21", "dev": true, "inBundle": true, "license": "CC0-1.0" @@ -9709,6 +10188,31 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", "dev": true, @@ -9721,6 +10225,48 @@ "inBundle": true, "license": "MIT" }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.14", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/npm/node_modules/treeverse": { "version": "3.0.0", "dev": true, @@ -9808,7 +10354,7 @@ } }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.0", + "version": "6.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -9967,15 +10513,18 @@ }, "node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9994,16 +10543,18 @@ } }, "node_modules/oidc-token-hash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", - "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", "license": "MIT", "engines": { "node": "^10.13.0 || >=12.0.0" } }, "node_modules/on-finished": { - "version": "2.3.0", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -10023,6 +10574,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -10030,22 +10583,24 @@ }, "node_modules/one-time": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", "dependencies": { "fn.name": "1.x.x" } }, "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=12" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10053,6 +10608,8 @@ }, "node_modules/ono": { "version": "7.1.3", + "resolved": "https://registry.npmjs.org/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ==", "license": "MIT", "dependencies": { "@jsdevtools/ono": "7.1.3" @@ -10060,6 +10617,8 @@ }, "node_modules/openapi-schema-validator": { "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-12.1.3.tgz", + "integrity": "sha512-xTHOmxU/VQGUgo7Cm0jhwbklOKobXby+/237EG967+3TQEYJztMgX9Q5UE2taZKwyKPUq0j11dngpGjUuxz1hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10069,8 +10628,28 @@ "openapi-types": "^12.1.3" } }, + "node_modules/openapi-schema-validator/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/openapi-types": { "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "dev": true, "license": "MIT" }, @@ -10089,8 +10668,19 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -10107,6 +10697,8 @@ }, "node_modules/p-each-series": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "license": "MIT", "engines": { "node": ">=8" @@ -10115,6 +10707,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-filter": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", @@ -10157,19 +10765,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-limit/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-locate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", @@ -10187,9 +10782,9 @@ } }, "node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { @@ -10212,23 +10807,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, "node_modules/package-json-from-dist": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -10302,11 +10914,15 @@ }, "node_modules/parse5-query-domtree": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse5-query-domtree/-/parse5-query-domtree-1.0.2.tgz", + "integrity": "sha512-5mmp13wtARQonN1RInX4R3p5Jx2AQtd2lYlTMk6G84ZZBUIHMNpvMmTzkEZ+kRbIvZDwX8uQopZIrFkawfJy3Q==", "dev": true, "license": "ISC" }, "node_modules/parseurl": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10314,6 +10930,8 @@ }, "node_modules/passport": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", "dependencies": { "passport-strategy": "1.x.x", @@ -10330,6 +10948,8 @@ }, "node_modules/passport-anonym-uuid": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/passport-anonym-uuid/-/passport-anonym-uuid-1.0.3.tgz", + "integrity": "sha512-bsu59r5hyU0zwqJ3EMdbN0O7O4oZmcjFNhVKXF2PAgPlm1TTJUizJnrkErj4mjOM/mtNE4FwbW4TusrXZJ4lLQ==", "dependencies": { "node-uuid": "^1.4.7", "passport-strategy": "1.x.x" @@ -10338,14 +10958,10 @@ "node": ">= 0.4.0" } }, - "node_modules/passport-anonym-uuid/node_modules/node-uuid": { - "version": "1.4.8", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/passport-http": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/passport-http/-/passport-http-0.3.0.tgz", + "integrity": "sha512-OwK9DkqGVlJfO8oD0Bz1VDIo+ijD3c1ZbGGozIZw+joIP0U60pXY7goB+8wiDWtNqHpkTaQiJ9Ux1jE3Ykmpuw==", "dependencies": { "passport-strategy": "1.x.x" }, @@ -10355,6 +10971,8 @@ }, "node_modules/passport-http-bearer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz", + "integrity": "sha512-SELQM+dOTuMigr9yu8Wo4Fm3ciFfkMq5h/ZQ8ffi4ELgZrX1xh9PlglqZdcUZ1upzJD/whVyt+YWF62s3U6Ipw==", "dependencies": { "passport-strategy": "1.x.x" }, @@ -10364,20 +10982,26 @@ }, "node_modules/passport-strategy": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", "engines": { "node": ">= 0.4.0" } }, "node_modules/path-exists": { - "version": "4.0.0", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -10386,6 +11010,8 @@ }, "node_modules/path-scurry": { "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -10401,17 +11027,16 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -10424,7 +11049,9 @@ } }, "node_modules/pause": { - "version": "0.0.1" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/pend": { "version": "1.2.0", @@ -10435,11 +11062,15 @@ }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -10526,16 +11157,6 @@ "node": ">=4" } }, - "node_modules/pkg-conf/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -10615,8 +11236,30 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -10624,12 +11267,11 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -10642,6 +11284,8 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "license": "MIT", "dependencies": { @@ -10653,6 +11297,8 @@ }, "node_modules/pretty-format": { "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10665,9 +11311,9 @@ } }, "node_modules/pretty-ms": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", - "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10682,6 +11328,8 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "license": "MIT" }, @@ -10694,6 +11342,8 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -10722,6 +11372,8 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -10742,29 +11394,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/random-bytes": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10772,6 +11405,8 @@ }, "node_modules/randombytes": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10802,35 +11437,6 @@ "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -10847,6 +11453,13 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -10859,11 +11472,15 @@ }, "node_modules/react-is": { "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, "node_modules/read-json-lines-sync": { "version": "2.2.5", + "resolved": "https://registry.npmjs.org/read-json-lines-sync/-/read-json-lines-sync-2.2.5.tgz", + "integrity": "sha512-yTQK/fkO0ZIKSMC26F3OKHwVnUZ9PVLSh0flrliwpBgoWPxck5SK3qIlZEfrBb1Uahh518p4637pK7e0EfutrQ==", "dev": true, "license": "MIT" }, @@ -10906,15 +11523,15 @@ } }, "node_modules/read-pkg/node_modules/parse-json": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", - "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "index-to-position": "^0.1.2", - "type-fest": "^4.7.1" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { "node": ">=18" @@ -10923,31 +11540,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/readable-stream": { - "version": "2.3.7", - "dev": true, + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/readdirp": { @@ -10979,6 +11583,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -10987,35 +11593,27 @@ }, "node_modules/require-from-string": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -11030,17 +11628,12 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", "license": "MIT" }, "node_modules/safe-stable-stringify": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -11053,17 +11646,16 @@ "license": "MIT" }, "node_modules/semantic-release": { - "version": "24.2.5", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.5.tgz", - "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==", + "version": "24.2.9", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.9.tgz", + "integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", "@semantic-release/github": "^11.0.0", - "@semantic-release/npm": "^12.0.0", + "@semantic-release/npm": "^12.0.2", "@semantic-release/release-notes-generator": "^14.0.0-beta.1", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", @@ -11074,7 +11666,7 @@ "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", - "hook-std": "^3.0.0", + "hook-std": "^4.0.0", "hosted-git-info": "^8.0.0", "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", @@ -11086,7 +11678,7 @@ "read-package-up": "^11.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", - "semver-diff": "^4.0.0", + "semver-diff": "^5.0.0", "signale": "^1.2.1", "yargs": "^17.5.1" }, @@ -11097,45 +11689,125 @@ "node": ">=20.8.1" } }, - "node_modules/semantic-release/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=12" + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/semantic-release/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": ">=6.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/semantic-release/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/semantic-release/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/semantic-release/node_modules/p-each-series": { "version": "3.0.0", @@ -11150,39 +11822,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/semantic-release/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/semantic-release/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/semantic-release/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11192,9 +11887,10 @@ } }, "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-5.0.0.tgz", + "integrity": "sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg==", + "deprecated": "Deprecated as the semver package now supports this built-in.", "dev": true, "license": "MIT", "dependencies": { @@ -11221,15 +11917,15 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", + "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", @@ -11244,35 +11940,62 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/send/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/serialize-javascript": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11294,17 +12017,108 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-cookie-parser": { - "version": "2.7.1", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "dev": true, "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -11316,6 +12130,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -11401,15 +12217,11 @@ "license": "MIT" }, "node_modules/signal-exit": { - "version": "4.1.0", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "ISC" }, "node_modules/signale": { "version": "1.4.0", @@ -11454,6 +12266,23 @@ "node": ">=4" } }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, "node_modules/signale/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -11500,13 +12329,6 @@ "node": ">=4" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/sinon": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", @@ -11540,6 +12362,8 @@ }, "node_modules/slash": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { @@ -11548,7 +12372,7 @@ }, "node_modules/sorted-array-functions": { "version": "1.3.0", - "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm-remote/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", "license": "MIT" }, @@ -11564,6 +12388,8 @@ }, "node_modules/sparse-bitfield": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" @@ -11606,24 +12432,26 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/split2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, "license": "ISC", - "dependencies": { - "through2": "~2.0.0" + "engines": { + "node": ">= 10.x" } }, "node_modules/stack-trace": { "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "license": "MIT", "engines": { "node": "*" @@ -11631,6 +12459,8 @@ }, "node_modules/stack-utils": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11640,8 +12470,20 @@ "node": ">=10" } }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { - "version": "2.0.1", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -11658,6 +12500,39 @@ "readable-stream": "^2.0.2" } }, + "node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-combiner2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-combiner2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -11667,28 +12542,30 @@ } }, "node_modules/streamx": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", - "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "dev": true, "license": "MIT", "dependencies": { + "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { - "version": "1.1.1", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11702,6 +12579,8 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -11713,8 +12592,72 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11723,14 +12666,12 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { "node": ">=8" } @@ -11746,20 +12687,19 @@ } }, "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/strip-json-comments": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -11770,13 +12710,14 @@ } }, "node_modules/super-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", - "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", "dev": true, "license": "MIT", "dependencies": { "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", "time-span": "^5.1.0" }, "engines": { @@ -11787,42 +12728,29 @@ } }, "node_modules/superagent": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.2.tgz", - "integrity": "sha512-vWMq11OwWCC84pQaFPzF/VO3BrjkCeewuvJgt1jfV0499Z1QSAWN4EqfMM5WlFDDX9/oP8JjlDKpblrmEoyu4Q==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "license": "MIT", "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0" + "qs": "^6.11.2" }, "engines": { "node": ">=14.18.0" } }, - "node_modules/superagent/node_modules/debug": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -11831,19 +12759,15 @@ "node": ">=4.0.0" } }, - "node_modules/superagent/node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, "node_modules/supertest": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.1.tgz", - "integrity": "sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, "license": "MIT", "dependencies": { "methods": "^1.1.2", - "superagent": "^10.2.1" + "superagent": "^10.2.3" }, "engines": { "node": ">=14.18.0" @@ -11851,6 +12775,8 @@ }, "node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -11878,7 +12804,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.18.2", + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz", + "integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -11886,6 +12814,8 @@ }, "node_modules/swagger-ui-express": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", "license": "MIT", "dependencies": { "swagger-ui-dist": ">=5.0.0" @@ -11898,13 +12828,13 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -11982,6 +12912,8 @@ }, "node_modules/test-exclude": { "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "license": "ISC", "dependencies": { @@ -11994,7 +12926,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12003,6 +12937,8 @@ }, "node_modules/test-exclude/node_modules/minimatch": { "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -12040,6 +12976,8 @@ }, "node_modules/text-hex": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, "node_modules/thenify": { @@ -12083,6 +13021,39 @@ "xtend": "~4.0.1" } }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", @@ -12100,14 +13071,67 @@ } }, "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12119,19 +13143,23 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/tr46": { - "version": "4.1.1", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/traverse": { @@ -12149,6 +13177,8 @@ }, "node_modules/triple-beam": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", "license": "MIT", "engines": { "node": ">= 14.0.0" @@ -12156,6 +13186,8 @@ }, "node_modules/tslib": { "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, @@ -12171,6 +13203,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -12191,9 +13225,9 @@ } }, "node_modules/type-fest": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", - "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -12217,25 +13251,20 @@ "node": ">= 0.6" } }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typedarray": { @@ -12245,9 +13274,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -12275,6 +13304,8 @@ }, "node_modules/uid-safe": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" @@ -12296,6 +13327,12 @@ "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", @@ -12307,9 +13344,9 @@ } }, "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, "license": "MIT", "engines": { @@ -12336,14 +13373,16 @@ } }, "node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", "dev": true, "license": "ISC" }, "node_modules/universalify": { - "version": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -12380,10 +13419,14 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -12403,13 +13446,15 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" @@ -12428,31 +13473,46 @@ }, "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "license": "BSD-2-Clause", "engines": { "node": ">=12" } }, "node_modules/whatwg-url": { - "version": "13.0.0", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -12466,11 +13526,13 @@ } }, "node_modules/winston": { - "version": "3.17.0", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", + "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", + "@dabh/diagnostics": "^2.0.8", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", @@ -12487,6 +13549,8 @@ }, "node_modules/winston-transport": { "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { "logform": "^2.7.0", @@ -12497,39 +13561,19 @@ "node": ">= 12.0.0" } }, - "node_modules/winston-transport/node_modules/readable-stream": { - "version": "3.6.2", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/winston/node_modules/@colors/colors": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", "engines": { "node": ">=0.1.90" } }, - "node_modules/winston/node_modules/readable-stream": { - "version": "3.6.0", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -12538,25 +13582,31 @@ }, "node_modules/wordwrap": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/workerpool": { - "version": "6.5.1", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true, "license": "Apache-2.0" }, "node_modules/wrap-ansi": { - "version": "7.0.0", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -12565,6 +13615,8 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -12579,8 +13631,20 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -12595,6 +13659,8 @@ }, "node_modules/wrap-ansi-cjs/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12606,45 +13672,72 @@ }, "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/xtend": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "license": "MIT", "engines": { "node": ">=0.4" @@ -12652,6 +13745,8 @@ }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -12660,27 +13755,33 @@ }, "node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, "node_modules/yargs": { - "version": "16.2.0", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -12689,6 +13790,8 @@ }, "node_modules/yargs-unparser": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "license": "MIT", "dependencies": { @@ -12701,25 +13804,6 @@ "node": ">=10" } }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yauzl": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", @@ -12735,20 +13819,22 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/yoctocolors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", - "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "dev": true, "license": "MIT", "engines": { @@ -12759,9 +13845,9 @@ } }, "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" From 876ebdc2a19beb473369345f7d958edf67661f4a Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 1 Jan 2026 11:52:33 -0500 Subject: [PATCH 145/370] fix: update old openapi definitions with newer object types --- app/api/definitions/paths/attack-objects-paths.yml | 11 ++++++++++- app/api/definitions/paths/recent-activity-paths.yml | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/api/definitions/paths/attack-objects-paths.yml b/app/api/definitions/paths/attack-objects-paths.yml index 2b4c536f..edaa64da 100644 --- a/app/api/definitions/paths/attack-objects-paths.yml +++ b/app/api/definitions/paths/attack-objects-paths.yml @@ -119,7 +119,16 @@ paths: - $ref: '../components/marking-definitions.yml#/components/schemas/marking-definition' - $ref: '../components/matrices.yml#/components/schemas/matrix' - $ref: '../components/mitigations.yml#/components/schemas/mitigation' - - $ref: '../components/relationships.yml#/components/schemas/relationship' + - $ref: '../components/software.yml#/components/schemas/software' + - $ref: '../components/tactics.yml#/components/schemas/tactic' + - $ref: '../components/techniques.yml#/components/schemas/technique' + - $ref: '../components/analytics.yml#/components/schemas/analytic' + - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' + - $ref: '../components/assets.yml#/components/schemas/asset' + - $ref: '../components/campaigns.yml#/components/schemas/campaign' + - $ref: '../components/data-components.yml#/components/schemas/data-component' + - $ref: '../components/data-sources.yml#/components/schemas/data-source' + - $ref: '../components/software.yml#/components/schemas/software' - $ref: '../components/tactics.yml#/components/schemas/tactic' - $ref: '../components/techniques.yml#/components/schemas/technique' diff --git a/app/api/definitions/paths/recent-activity-paths.yml b/app/api/definitions/paths/recent-activity-paths.yml index c4c98466..069baa29 100644 --- a/app/api/definitions/paths/recent-activity-paths.yml +++ b/app/api/definitions/paths/recent-activity-paths.yml @@ -76,3 +76,9 @@ paths: - $ref: '../components/software.yml#/components/schemas/software' - $ref: '../components/tactics.yml#/components/schemas/tactic' - $ref: '../components/techniques.yml#/components/schemas/technique' + - $ref: '../components/analytics.yml#/components/schemas/analytic' + - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' + - $ref: '../components/assets.yml#/components/schemas/asset' + - $ref: '../components/campaigns.yml#/components/schemas/campaign' + - $ref: '../components/data-components.yml#/components/schemas/data-component' + - $ref: '../components/data-sources.yml#/components/schemas/data-source' From ab15415aa5007eadc2ab9c97026721d34268f803 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 1 Jan 2026 11:55:51 -0500 Subject: [PATCH 146/370] feat: add URLs for parallel relationships and missing-linkbyid --- app/api/definitions/openapi.yml | 10 +++ .../paths/attack-objects-paths.yml | 39 +++++++++ .../definitions/paths/relationships-paths.yml | 53 ++++++++++++ app/controllers/attack-objects-controller.js | 11 +++ app/controllers/relationships-controller.js | 22 +++++ app/repository/attack-objects-repository.js | 20 +++++ app/repository/relationships-repository.js | 31 +++++++ app/routes/attack-objects-routes.js | 8 ++ app/routes/relationships-routes.js | 16 ++++ app/services/stix/attack-objects-service.js | 10 +++ app/services/stix/relationships-service.js | 15 ++++ .../api/attack-objects/attack-objects.spec.js | 20 ++++- .../api/relationships/relationships.spec.js | 85 +++++++++++++++++++ 13 files changed, 339 insertions(+), 1 deletion(-) diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 0eace35f..05838053 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -72,6 +72,10 @@ paths: /api/attack-objects: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects' + # Find objects with missing LinkById tags + /api/attack-objects/missing-linkbyid: + $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects~1missing-linkbyid' + # Generate next available ATT&CK ID /api/attack-objects/attack-id/next: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects~1attack-id~1next' @@ -232,6 +236,12 @@ paths: /api/relationships: $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships' + /api/relationships/missing-linkbyid: + $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships~1missing-linkbyid' + + /api/relationships/parallel: + $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships~1parallel' + /api/relationships/{stixId}: $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships~1{stixId}' diff --git a/app/api/definitions/paths/attack-objects-paths.yml b/app/api/definitions/paths/attack-objects-paths.yml index edaa64da..8575a862 100644 --- a/app/api/definitions/paths/attack-objects-paths.yml +++ b/app/api/definitions/paths/attack-objects-paths.yml @@ -129,9 +129,48 @@ paths: - $ref: '../components/data-components.yml#/components/schemas/data-component' - $ref: '../components/data-sources.yml#/components/schemas/data-source' + /api/attack-objects/missing-linkbyid: + get: + summary: 'Get a list of ATT&CK objects with missing LinkById tags' + operationId: 'attack-object-get-missing-linkbyid' + description: | + This endpoint generates a list of ATT&CK objects that directly mention + attack.mitre.org in their descriptions, indicating that they are likely missing a + reference using the (LinkById: X####) pattern. + tags: + - 'ATT&CK Objects' + responses: + '200': + description: 'A list of ATT&CK Objects.' + content: + application/json: + schema: + type: array + items: + anyOf: + - $ref: '../components/collections.yml#/components/schemas/collection' + - $ref: '../components/groups.yml#/components/schemas/group' + - $ref: '../components/identities.yml#/components/schemas/identity' + - $ref: '../components/marking-definitions.yml#/components/schemas/marking-definition' + - $ref: '../components/matrices.yml#/components/schemas/matrix' + - $ref: '../components/mitigations.yml#/components/schemas/mitigation' - $ref: '../components/software.yml#/components/schemas/software' - $ref: '../components/tactics.yml#/components/schemas/tactic' - $ref: '../components/techniques.yml#/components/schemas/technique' + - $ref: '../components/analytics.yml#/components/schemas/analytic' + - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' + - $ref: '../components/assets.yml#/components/schemas/asset' + - $ref: '../components/campaigns.yml#/components/schemas/campaign' + - $ref: '../components/data-components.yml#/components/schemas/data-component' + - $ref: '../components/data-sources.yml#/components/schemas/data-source' + '500': + description: 'Server error' + content: + text/plain: + schema: + type: string + example: 'Server error.' + /api/attack-objects/attack-id/next: get: summary: 'Get the next available ATT&CK ID for a STIX type' diff --git a/app/api/definitions/paths/relationships-paths.yml b/app/api/definitions/paths/relationships-paths.yml index 85476bce..118e77c2 100644 --- a/app/api/definitions/paths/relationships-paths.yml +++ b/app/api/definitions/paths/relationships-paths.yml @@ -187,6 +187,59 @@ paths: '409': description: 'Duplicate `stix.id` and `stix.modified` properties. The relationship was not created.' + /api/relationships/missing-linkbyid: + get: + summary: 'Get a list of relationships with missing LinkById tags' + operationId: 'relationship-get-missing-linkbyid' + description: | + This endpoint generates a list of relationships that directly mention + attack.mitre.org in their descriptions, indicating that they are likely missing a + reference using the (LinkById: X####) pattern. + tags: + - 'Relationships' + responses: + '200': + description: 'A list of relationships.' + content: + application/json: + schema: + type: array + items: + $ref: '../components/relationships.yml#/components/schemas/relationship' + '500': + description: 'Server error' + content: + text/plain: + schema: + type: string + example: 'Server error.' + + /api/relationships/parallel: + get: + summary: 'Get a list of relationships with matching sources and targets.' + operationId: 'relationship-get-parallel' + description: | + This endpoint generates a list of lists of parallel relationships. Within each list, + the members share the same source_ref and target_ref values. + tags: + - 'Relationships' + responses: + '200': + description: 'A list of lists of parallel relationships.' + content: + application/json: + schema: + type: array + items: + $ref: '../components/relationships.yml#/components/schemas/relationship' + '500': + description: 'Server error' + content: + text/plain: + schema: + type: string + example: 'Server error.' + /api/relationships/{stixId}: get: summary: 'Get one or more versions of a relationship' diff --git a/app/controllers/attack-objects-controller.js b/app/controllers/attack-objects-controller.js index 263b72de..77541232 100644 --- a/app/controllers/attack-objects-controller.js +++ b/app/controllers/attack-objects-controller.js @@ -35,6 +35,17 @@ exports.retrieveAll = async function (req, res) { } }; +exports.getObjectsMissingLinkById = async function (_, res) { + try { + const results = await attackObjectsService.retrieveAllWithAttackURLInDescription(); + logger.debug(`Success: Retrieved ${results.length} ATT&CK object(s)`); + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get ATT&CK objects. Server error.'); + } +}; + exports.getNextAttackId = async function (req, res) { // Validate required query parameter if (!req.query.type) { diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index 5ce19c77..e9294586 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -96,6 +96,28 @@ exports.retrieveVersionById = async function (req, res) { } }; +exports.getRelationshipsMissingLinkById = async function (_, res) { + try { + const results = await relationshipsService.retrieveAllWithAttackURLInDescription(); + logger.debug(`Success: Retrieved ${results.length} relationship(s)`); + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get relationships. Server error.'); + } +}; + +exports.getParallelRelationships = async function (_, res) { + try { + const results = await relationshipsService.retrieveParallelRelationships(); + logger.debug(`Success: Retrieved ${results.length} set(s) of parallel relationship(s)`); + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get relationships. Server error.'); + } +}; + exports.create = async function (req, res) { // Get the data from the request const relationshipData = req.body; diff --git a/app/repository/attack-objects-repository.js b/app/repository/attack-objects-repository.js index 0adba68c..81b342f3 100644 --- a/app/repository/attack-objects-repository.js +++ b/app/repository/attack-objects-repository.js @@ -95,6 +95,26 @@ class AttackObjectsRepository extends BaseRepository { ]; } + async retrieveAllWithAttackURLInDescription() { + const aggregation = [ + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { $sort: { 'stix.id': 1 } }, + { + $match: { + 'stix.revoked': { $in: [null, false] }, + 'stix.x_mitre_deprecated': { $in: [null, false] }, + 'stix.description': { $regex: 'attack.mitre.org', $options: 'i' }, + }, + }, + ]; + + const documents = await this.model.aggregate(aggregation).exec(); + + return documents; + } + // A lean variant of BaseService.retrieveOneByVersion // TODO merge the two methods by supporting method argument 'lean=false' that toggles .lean() on/off async retrieveOneByVersionLean(stixId, modified) { diff --git a/app/repository/relationships-repository.js b/app/repository/relationships-repository.js index 766e4a4b..aeb860a0 100644 --- a/app/repository/relationships-repository.js +++ b/app/repository/relationships-repository.js @@ -110,6 +110,37 @@ class RelationshipsRepository extends BaseRepository { throw new DatabaseError(err); } } + + async retrieveAllWithAttackURLInDescription() { + const aggregation = [ + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { $sort: { 'stix.id': 1 } }, + { + $match: { + 'stix.revoked': { $in: [null, false] }, + 'stix.x_mitre_deprecated': { $in: [null, false] }, + 'stix.description': { $regex: 'attack.mitre.org', $options: 'i' }, + }, + }, + ]; + + return await this.model.aggregate(aggregation).exec(); + } + + async retrieveParallelRelationships() { + const all_relationships = this.retrieveAll({ versions: 'latest' }); + let rel_map = new Map(); + for (const rel of all_relationships) { + const rel_key = + rel.stix.source_ref + '--' + rel.stix.relationship_type + '--' + rel.stix.target_ref; + if (!rel_map.has(rel_key)) { + rel_map[rel_key] = []; + } + rel_map[rel_key].push(rel.stix.id); + } + } } module.exports = new RelationshipsRepository(Relationship); diff --git a/app/routes/attack-objects-routes.js b/app/routes/attack-objects-routes.js index 91b04a62..3d11299c 100644 --- a/app/routes/attack-objects-routes.js +++ b/app/routes/attack-objects-routes.js @@ -16,6 +16,14 @@ router attackObjectsController.retrieveAll, ); +router + .route('/attack-objects/missing-linkbyid') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + attackObjectsController.getObjectsMissingLinkById, + ); + router .route('/attack-objects/attack-id/next') .get( diff --git a/app/routes/relationships-routes.js b/app/routes/relationships-routes.js index 341c0644..83c90e1b 100644 --- a/app/routes/relationships-routes.js +++ b/app/routes/relationships-routes.js @@ -21,6 +21,22 @@ router relationshipsController.create, ); +router + .route('/relationships/missing-linkbyid') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + relationshipsController.getRelationshipsMissingLinkById, + ); + +router + .route('/relationships/parallel') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + relationshipsController.getParallelRelationships, + ); + router .route('/relationships/:stixId') .get( diff --git a/app/services/stix/attack-objects-service.js b/app/services/stix/attack-objects-service.js index f08f073c..717cfa03 100644 --- a/app/services/stix/attack-objects-service.js +++ b/app/services/stix/attack-objects-service.js @@ -50,6 +50,16 @@ class AttackObjectsService extends BaseService { return documents; } + async retrieveAllWithAttackURLInDescription() { + // Get attack objects from repository + let documents = await this.repository.retrieveAllWithAttackURLInDescription(); + + // Add identities + await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(documents); + + return documents; + } + /** * Override of base class retrieveVersionById() because: * 1. Adds special handling for relationships diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index 68b356c6..fa23ff66 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -98,6 +98,21 @@ class RelationshipsService extends BaseService { return results; } } + + async retrieveAllWithAttackURLInDescription() { + let documents = await this.repository.retrieveAllWithAttackURLInDescription(); + await this.addCreatedByAndModifiedByIdentitiesToAll(documents); + + return documents; + } + + async getParallelRelationships() { + let documents = await this.repository.retrieveParallelRelationships(); + for (let i = 0; i < documents.length; i++) { + await this.addCreatedByAndModifiedByIdentitiesToAll(documents[i]); + } + return documents; + } } // Default export diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index ef16c8e1..81a59a6e 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -24,7 +24,8 @@ const malwareObject = { name: 'software-2', spec_version: '2.1', type: 'malware', - description: 'This is a malware type of software.', + description: + 'This is a malware type of software, with a URL that it should not have (https://attack.mitre.org/software/SW0001)', is_family: false, object_marking_refs: ['marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce'], x_mitre_version: '1.1', @@ -265,6 +266,23 @@ describe('ATT&CK Objects API', function () { expect(attackObjects[0].stix.type).toBe('malware'); }); + it('GET /api/attack-objects/missing-linkbyid returns the object with an attack.mitre.org URL in the description', async function () { + const res = await request(app) + .get('/api/attack-objects/missing-linkbyid') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + // We expect to get ATT&CK objects in an array + const attackObjects = res.body; + expect(attackObjects).toBeDefined(); + expect(Array.isArray(attackObjects)).toBe(true); + + expect(attackObjects.length).toBe(1); + expect(attackObjects[0].stix.name).toBe('software-2'); + }); + after(async function () { await database.closeConnection(); }); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 2e239929..4a4445e3 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -360,6 +360,67 @@ describe('Relationships API', function () { expect(relationship2).toBeDefined(); }); + let relationship3a; + it('POST /api/relationships creates a parallel relationship', async function () { + const timestamp = new Date().toISOString(); + initialObjectData.stix.created = timestamp; + initialObjectData.stix.modified = timestamp; + initialObjectData.stix.source_ref = sourceRef2; + initialObjectData.stix.target_ref = targetRef2; + const body = initialObjectData; + const res = await request(app) + .post('/api/relationships') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + // We expect to get the created relationship + relationship3a = res.body; + expect(relationship3a).toBeDefined(); + }); + + let relationship3b; + it('POST /api/relationships creates a parallel relationship with a different description', async function () { + const timestamp = new Date().toISOString(); + initialObjectData.stix.created = timestamp; + initialObjectData.stix.modified = timestamp; + initialObjectData.stix.source_ref = sourceRef2; + initialObjectData.stix.target_ref = targetRef2; + initialObjectData.stix.description = + 'This is a different description with a URL that it should not have (https://attack.mitre.org/foo/bar).'; + const body = initialObjectData; + const res = await request(app) + .post('/api/relationships') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + // We expect to get the created relationship + relationship3b = res.body; + expect(relationship3b).toBeDefined(); + }); + + it('GET /api/relationships/missing-linkbyid returns the relationship with an attack.mitre.org URL in the description', async function () { + const res = await request(app) + .get('/api/relationships/missing-linkbyid') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + // We expect to get ATT&CK objects in an array + const mlRelationships = res.body; + expect(mlRelationships).toBeDefined(); + expect(Array.isArray(mlRelationships)).toBe(true); + + expect(mlRelationships.length).toBe(1); + expect(mlRelationships[0].stix.source_ref).toBe(sourceRef2); + }); + it('GET /api/relationships returns the (latest) relationship matching a source_ref', async function () { const res = await request(app) .get('/api/relationships?sourceRef=' + sourceRef1) @@ -485,6 +546,30 @@ describe('Relationships API', function () { .expect(204); }); + it('DELETE /api/relationships should delete the fourth relationship', async function () { + await request(app) + .delete( + '/api/relationships/' + + relationship3a.stix.id + + '/modified/' + + relationship3a.stix.modified, + ) + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(204); + }); + + it('DELETE /api/relationships should delete the fifth relationship', async function () { + await request(app) + .delete( + '/api/relationships/' + + relationship3b.stix.id + + '/modified/' + + relationship3b.stix.modified, + ) + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(204); + }); + it('GET /api/relationships returns an empty array of relationships', async function () { const res = await request(app) .get('/api/relationships') From e7eb452b1b80d2b5825288ae4cad6dd071ea5ee3 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:45:16 -0500 Subject: [PATCH 147/370] feat: fill in most of the missing pieces of /api/relationships/parallel --- .../definitions/paths/relationships-paths.yml | 12 ++++++------ app/controllers/relationships-controller.js | 2 +- app/repository/relationships-repository.js | 18 +++++++++++++++--- app/services/stix/relationships-service.js | 10 +++++----- .../api/relationships/relationships.spec.js | 18 ++++++++++++++++++ 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/app/api/definitions/paths/relationships-paths.yml b/app/api/definitions/paths/relationships-paths.yml index 118e77c2..f65a9423 100644 --- a/app/api/definitions/paths/relationships-paths.yml +++ b/app/api/definitions/paths/relationships-paths.yml @@ -216,21 +216,21 @@ paths: /api/relationships/parallel: get: - summary: 'Get a list of relationships with matching sources and targets.' + summary: 'Get a list of relationships with matching sources, targets, and relationship types.' operationId: 'relationship-get-parallel' description: | - This endpoint generates a list of lists of parallel relationships. Within each list, - the members share the same source_ref and target_ref values. + This endpoint generates a map of lists of parallel relationships. Within each list, + the members share the same source_ref, target_ref, and relationship type values. tags: - 'Relationships' responses: '200': - description: 'A list of lists of parallel relationships.' + description: 'A map of lists of parallel relationships.' content: application/json: schema: - type: array - items: + type: object + additionalProperties: $ref: '../components/relationships.yml#/components/schemas/relationship' '500': description: 'Server error' diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index e9294586..551ba589 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -109,7 +109,7 @@ exports.getRelationshipsMissingLinkById = async function (_, res) { exports.getParallelRelationships = async function (_, res) { try { - const results = await relationshipsService.retrieveParallelRelationships(); + const results = await relationshipsService.getParallelRelationships(); logger.debug(`Success: Retrieved ${results.length} set(s) of parallel relationship(s)`); return res.status(200).send(results); } catch (err) { diff --git a/app/repository/relationships-repository.js b/app/repository/relationships-repository.js index aeb860a0..21f76a1b 100644 --- a/app/repository/relationships-repository.js +++ b/app/repository/relationships-repository.js @@ -130,16 +130,28 @@ class RelationshipsRepository extends BaseRepository { } async retrieveParallelRelationships() { - const all_relationships = this.retrieveAll({ versions: 'latest' }); + const all_relationships = await this.retrieveAll({ versions: 'latest' }); + + // Create a mapping of rel_key (source_ref--relationship_type--target_ref) + // to an array of relationships that share it. let rel_map = new Map(); for (const rel of all_relationships) { const rel_key = rel.stix.source_ref + '--' + rel.stix.relationship_type + '--' + rel.stix.target_ref; if (!rel_map.has(rel_key)) { - rel_map[rel_key] = []; + rel_map.set(rel_key, []); } - rel_map[rel_key].push(rel.stix.id); + const entry = rel_map.get(rel_key); + entry.push(rel); } + + // return rel_map; + // Filter out the rel_keys that have more than one item in the array. + const parallel_relationships = new Map( + [...rel_map.entries()].filter(([, value]) => value.length > 1), + ); + + return parallel_relationships; } } diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index fa23ff66..9f1f1e7d 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -107,11 +107,11 @@ class RelationshipsService extends BaseService { } async getParallelRelationships() { - let documents = await this.repository.retrieveParallelRelationships(); - for (let i = 0; i < documents.length; i++) { - await this.addCreatedByAndModifiedByIdentitiesToAll(documents[i]); - } - return documents; + let relationship_map = await this.repository.retrieveParallelRelationships(); + relationship_map.forEach(async (value) => { + await this.addCreatedByAndModifiedByIdentitiesToAll(value); + }); + return relationship_map; } } diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 4a4445e3..d7c6c1b6 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -421,6 +421,24 @@ describe('Relationships API', function () { expect(mlRelationships[0].stix.source_ref).toBe(sourceRef2); }); + it('GET /api/relationships/parallel returns the parallel relationships', async function () { + const res = await request(app) + .get('/api/relationships/parallel') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + console.log(res.body); + // We expect to get ATT&CK objects in an array + const parallelRelationships = res.body; + expect(parallelRelationships).toBeDefined(); + expect(Array.isArray(parallelRelationships)).toBe(true); + + expect(parallelRelationships.length).toBe(1); + expect(parallelRelationships[0].stix.source_ref).toBe(sourceRef2); + }); + it('GET /api/relationships returns the (latest) relationship matching a source_ref', async function () { const res = await request(app) .get('/api/relationships?sourceRef=' + sourceRef1) From 2ff8a7518bf36ef502d30d3de5f9fb11cb06dd08 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:47:12 -0500 Subject: [PATCH 148/370] chore: add comments to show where parallel relationships aren't returning properly --- app/controllers/relationships-controller.js | 2 ++ app/repository/relationships-repository.js | 3 +-- app/tests/api/relationships/relationships.spec.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index 551ba589..03439cb5 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -110,6 +110,8 @@ exports.getRelationshipsMissingLinkById = async function (_, res) { exports.getParallelRelationships = async function (_, res) { try { const results = await relationshipsService.getParallelRelationships(); + // console.log("parallel relationships:"); + // console.log(results); logger.debug(`Success: Retrieved ${results.length} set(s) of parallel relationship(s)`); return res.status(200).send(results); } catch (err) { diff --git a/app/repository/relationships-repository.js b/app/repository/relationships-repository.js index 21f76a1b..40e15176 100644 --- a/app/repository/relationships-repository.js +++ b/app/repository/relationships-repository.js @@ -145,8 +145,7 @@ class RelationshipsRepository extends BaseRepository { entry.push(rel); } - // return rel_map; - // Filter out the rel_keys that have more than one item in the array. + // Return only the rel_keys that have more than one item in the array. const parallel_relationships = new Map( [...rel_map.entries()].filter(([, value]) => value.length > 1), ); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index d7c6c1b6..aabdc525 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -429,7 +429,7 @@ describe('Relationships API', function () { .expect(200) .expect('Content-Type', /json/); - console.log(res.body); + // console.log(res.body); // We expect to get ATT&CK objects in an array const parallelRelationships = res.body; expect(parallelRelationships).toBeDefined(); From 475d92d0db8c7d0ef45075cbf65b230d808cea28 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Thu, 1 Jan 2026 12:50:34 -0500 Subject: [PATCH 149/370] fix: show number of keys in parallel relationship map --- app/controllers/relationships-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index 03439cb5..0a9a0f8f 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -112,7 +112,9 @@ exports.getParallelRelationships = async function (_, res) { const results = await relationshipsService.getParallelRelationships(); // console.log("parallel relationships:"); // console.log(results); - logger.debug(`Success: Retrieved ${results.length} set(s) of parallel relationship(s)`); + logger.debug( + `Success: Retrieved ${Object.keys(results).length} set(s) of parallel relationship(s)`, + ); return res.status(200).send(results); } catch (err) { logger.error('Failed with error: ' + err); From 3824794cb127d96a36779891cfe1be871c218db8 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:01:36 -0500 Subject: [PATCH 150/370] chore: update comments --- app/tests/api/relationships/relationships.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index aabdc525..0e72c395 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -412,7 +412,7 @@ describe('Relationships API', function () { .expect(200) .expect('Content-Type', /json/); - // We expect to get ATT&CK objects in an array + // We expect to get one relationship in an array const mlRelationships = res.body; expect(mlRelationships).toBeDefined(); expect(Array.isArray(mlRelationships)).toBe(true); @@ -430,7 +430,7 @@ describe('Relationships API', function () { .expect('Content-Type', /json/); // console.log(res.body); - // We expect to get ATT&CK objects in an array + // We expect to get a mapping of relationship key -> list of three parallel relationships const parallelRelationships = res.body; expect(parallelRelationships).toBeDefined(); expect(Array.isArray(parallelRelationships)).toBe(true); From 07bac59a597ec220d7e643bbbeee5a75b51ab283 Mon Sep 17 00:00:00 2001 From: Evan Lucchesi Leon <189633144+elucchesileon@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:09:09 -0500 Subject: [PATCH 151/370] fix: update schema for response to /api/relationships/parallel --- app/api/definitions/paths/relationships-paths.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/api/definitions/paths/relationships-paths.yml b/app/api/definitions/paths/relationships-paths.yml index f65a9423..dd13f458 100644 --- a/app/api/definitions/paths/relationships-paths.yml +++ b/app/api/definitions/paths/relationships-paths.yml @@ -231,7 +231,9 @@ paths: schema: type: object additionalProperties: - $ref: '../components/relationships.yml#/components/schemas/relationship' + type: array + items: + $ref: '../components/relationships.yml#/components/schemas/relationship' '500': description: 'Server error' content: From d80673b251ccd3f4fcdff0df6058a0707f5eb1d6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 6 Jan 2026 16:40:53 -0500 Subject: [PATCH 152/370] build(ci): remove 'adm' branch build target and release tag --- .github/workflows/ci.yml | 1 - .releaserc | 4 ---- 2 files changed, 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f63c2167..fecf9d1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: - beta - alpha - '*.x' # Matches branches like '1.x', '2.x' - - adm # TODO temporary project branch, remove when done pull_request: # Run on all PRs regardless of target branch workflow_dispatch: diff --git a/.releaserc b/.releaserc index ad14f01e..60f0d3b7 100644 --- a/.releaserc +++ b/.releaserc @@ -11,10 +11,6 @@ { "name": "alpha", "prerelease": true - }, - { - "name": "adm", - "prerelease": true } ], "plugins": [ From d2b7569081398c80f0ae1bb397577feeae3155e7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 6 Jan 2026 16:54:43 -0500 Subject: [PATCH 153/370] refactor: remove or revert code smells picked up during development --- app/api/definitions/components/techniques.yml | 4 +-- app/api/definitions/paths/assets-paths.yml | 28 ------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/app/api/definitions/components/techniques.yml b/app/api/definitions/components/techniques.yml index 1562fa12..34e92122 100644 --- a/app/api/definitions/components/techniques.yml +++ b/app/api/definitions/components/techniques.yml @@ -9,9 +9,9 @@ components: workspace: $ref: 'workspace.yml#/components/schemas/workspace' stix: - $ref: '#/components/schemas/attack-pattern' + $ref: '#/components/schemas/stix-attack-pattern' - attack-pattern: + stix-attack-pattern: allOf: - $ref: 'stix-common.yml#/components/schemas/stix-common' - type: object diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index ed98919c..76aec764 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -293,31 +293,3 @@ paths: '404': description: 'An asset with the requested STIX id and modified date was not found.' - # /api/assets/validate: - # post: - # summary: 'Validate an asset or a field of an asset' - # operationId: 'asset-validate' - # description: | - # This endpoint validates an asset object or a field of an asset without creating or updating it. - # tags: - # - 'Assets' - # requestBody: - # required: true - # content: - # application/json: - # schema: - # $ref: '../components/assets.yml#/components/schemas/asset' - # responses: - # '200': - # description: 'Validation result.' - # content: - # application/json: - # schema: - # type: object - # properties: - # valid: - # type: boolean - # errors: - # type: array - # items: - # type: string From 1a1f4e0906706eea131d1d57e06137d27a6f1cac Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:20:49 -0500 Subject: [PATCH 154/370] refactor: remove or revert code smells picked up during development --- app/tests/api/analytics/analytics-includeRefs.spec.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index fe607a84..35364480 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -10,10 +10,6 @@ const login = require('../../shared/login'); const logger = require('../../../lib/logger'); logger.level = 'debug'; -const { createSyntheticStixObject } = require('@mitre-attack/attack-data-model/dist/generator'); - -createSyntheticStixObject(); - // Test data for analytics with data component references const analyticData = { workspace: { From 32ce5f956f51c0f81bf23b5d6ea3f19102eb007c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:45:21 -0500 Subject: [PATCH 155/370] build(npm): regenerate lockfile --- package-lock.json | 13478 ++++++++++++++++++++++---------------------- 1 file changed, 6887 insertions(+), 6591 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32f29013..19dd0797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -461,9 +461,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -591,20 +591,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -647,6 +637,16 @@ "heap": ">= 0.2.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -699,6 +699,49 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -753,6 +796,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -770,6 +829,26 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -805,9 +884,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.2.tgz", - "integrity": "sha512-4MzEkYcXqe5rWnlj6Iy/aE4+wXkAv3q7rJUOHairl24HtiTqDHxVavz96KJBNNpRyIcEXTlCm93AQpVp/uR4MA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.6.0.tgz", + "integrity": "sha512-bhmVJYSGqZnnhEWeQT5lP405+/qXQOZqeNalINP1qnckscz6Jo/JB6AJxwpz0OvJvHhWod8Vvm+3aUvHP2ID8Q==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", @@ -829,9 +908,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", - "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz", + "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -914,6 +993,39 @@ "dev": true, "license": "MIT" }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.1" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", + "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^26.0.0" + } + }, "node_modules/@octokit/plugin-retry": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", @@ -1117,25 +1229,6 @@ "node": ">=18" } }, - "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-writer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", - "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-commits-filter": "^5.0.0", - "handlebars": "^4.7.7", - "meow": "^13.0.0", - "semver": "^7.5.2" - }, - "bin": { - "conventional-changelog-writer": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", @@ -1206,39 +1299,6 @@ "semantic-release": ">=24.1.0" } }, - "node_modules/@semantic-release/github/node_modules/@octokit/openapi-types": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", - "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@semantic-release/github/node_modules/@octokit/plugin-paginate-rest": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", - "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^15.0.1" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@semantic-release/github/node_modules/@octokit/types": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", - "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^26.0.0" - } - }, "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", @@ -1249,54 +1309,6 @@ "node": ">=18" } }, - "node_modules/@semantic-release/github/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/@semantic-release/github/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@semantic-release/github/node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, "node_modules/@semantic-release/npm": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", @@ -1363,9 +1375,9 @@ } }, "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", "dev": true, "license": "MIT", "dependencies": { @@ -1430,167 +1442,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", - "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], - "dev": true, - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.1", - "@npmcli/config": "^9.0.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.2.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.2.2", - "@npmcli/run-script": "^9.1.0", - "@sigstore/tuf": "^3.1.1", - "abbrev": "^3.0.1", - "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.4.1", - "ci-info": "^4.2.0", - "cli-columns": "^4.0.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^10.4.5", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.1.0", - "ini": "^5.0.0", - "init-package-json": "^7.0.2", - "is-cidr": "^5.1.1", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.1", - "libnpmexec": "^9.0.1", - "libnpmfund": "^6.0.1", - "libnpmhook": "^11.0.0", - "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.1", - "libnpmpublish": "^10.0.1", - "libnpmsearch": "^8.0.0", - "libnpmteam": "^7.0.0", - "libnpmversion": "^7.0.0", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^11.2.0", - "nopt": "^8.1.0", - "normalize-package-data": "^7.0.0", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.2", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^7.0.3", - "pacote": "^19.0.1", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "qrcode-terminal": "^0.12.0", - "read": "^4.1.0", - "semver": "^7.7.2", - "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^9.4.0", - "tar": "^6.2.1", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.1", - "which": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/@semantic-release/npm/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -1608,3157 +1459,3348 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "license": "MIT", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, - "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/@semantic-release/release-notes-generator": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", + "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^2.0.0", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" }, "engines": { - "node": ">=12" + "node": ">=20.8.1" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^7.0.4" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/agent": { - "version": "3.0.0", + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.1", + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/metavuln-calculator": "^8.0.0", - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.1", - "@npmcli/query": "^4.0.0", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "bin-links": "^5.0.0", - "cacache": "^19.0.1", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^8.0.0", - "npm-install-checks": "^7.1.0", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.1", - "pacote": "^19.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "proggy": "^3.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "ssri": "^12.0.0", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" - }, - "bin": { - "arborist": "bin/index.js" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/config": { - "version": "9.0.0", + "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", - "ci-info": "^4.0.0", - "ini": "^5.0.0", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "walk-up-path": "^3.0.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/fs": { - "version": "4.0.0", + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.3", + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.2", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "type-detect": "4.0.8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "8.0.1", + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "cacache": "^19.0.0", - "json-parse-even-better-errors": "^4.0.0", - "pacote": "^20.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { - "version": "20.0.0", + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "3.0.0", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.2.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", - "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.1", + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", + "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.2.2", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "dev": true, - "inBundle": true, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.1.1", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" + "dependencies": { + "@types/istanbul-lib-report": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/abbrev": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/agent-base": { - "version": "7.1.3", - "dev": true, - "inBundle": true, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "license": "MIT", - "engines": { - "node": ">= 14" + "dependencies": { + "@types/ms": "*", + "@types/node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@types/express": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", - "dev": true, - "inBundle": true, + "node_modules/@types/node": { + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "undici-types": "~7.16.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/aproba": { - "version": "2.0.0", + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "dev": true, - "inBundle": true, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "inBundle": true, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/bin-links": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", "dependencies": { - "cmd-shim": "^7.0.0", - "npm-normalize-package-bin": "^4.0.0", - "proc-log": "^5.0.0", - "read-cmd-shim": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/binary-extensions": { - "version": "2.3.0", - "dev": true, - "inBundle": true, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.2", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, - "inBundle": true, + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", "license": "MIT", + "peer": true, "dependencies": { - "balanced-match": "^1.0.0" + "@types/webidl-conversions": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache": { - "version": "19.0.1", + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "@types/yargs-parser": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "inBundle": true, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/chalk": { - "version": "5.4.1", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "inBundle": true, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "bin": { + "acorn": "bin/acorn" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/chownr": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", "engines": { - "node": ">=10" + "node": ">=0.4.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ci-info": { - "version": "4.2.0", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, "license": "MIT", - "engines": { - "node": ">=8" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.3", + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 14" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", + "node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cmd-shim": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "inBundle": true, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "inBundle": true, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "peerDependencies": { + "ajv": "^8.5.0" }, - "engines": { - "node": ">= 8" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "ajv": "^8.0.0" }, - "bin": { - "node-which": "bin/node-which" + "peerDependencies": { + "ajv": "^8.0.0" }, - "engines": { - "node": ">= 8" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, - "inBundle": true, "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" + "dependencies": { + "environment": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/debug": { - "version": "4.4.1", + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/diff": { + "node_modules/ansi-styles": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "inBundle": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/err-code": { - "version": "2.0.3", + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.2", - "dev": true, - "inBundle": true, - "license": "Apache-2.0" + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true, - "inBundle": true, + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-await-retry": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/async-await-retry/-/async-await-retry-2.1.0.tgz", + "integrity": "sha512-eP0cVR8SfTussawaGL1edKe6aQJiVo2A8+TFBhpg3GKMHcn3FvAT/CfdaPtXdbsLsca/L7qwhi7e8D7SDFnoNQ==", "license": "MIT", + "bin": { + "async-await-retry": "index.js" + }, "engines": { - "node": ">= 4.9.1" + "node": ">=7.6.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/foreground-child": { - "version": "3.3.1", + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "tslib": "^2.4.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/glob": { - "version": "10.4.5", + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "inBundle": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/hosted-git-info": { - "version": "8.1.0", + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.2.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.2", - "dev": true, - "inBundle": true, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "safe-buffer": "5.1.2" }, "engines": { - "node": ">= 14" + "node": ">= 0.8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.6", + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, - "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 14" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "inBundle": true, "license": "MIT", - "optional": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ignore-walk": { - "version": "7.0.0", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minimatch": "^9.0.0" + "fill-range": "^7.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "ISC" + }, + "node_modules/bson": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", + "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=0.8.19" + "node": ">=20.19.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ini": { - "version": "5.0.0", + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "*" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/init-package-json": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/package-json": "^6.0.0", - "npm-package-arg": "^12.0.0", - "promzard": "^2.0.0", - "read": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^6.0.0" + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10.16.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", - "dev": true, - "inBundle": true, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" }, "engines": { - "node": ">= 12" + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ip-regex": { + "node_modules/c8/node_modules/find-up": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/is-cidr": { - "version": "5.1.1", + "node_modules/c8/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "cidr-regex": "^4.1.1" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/c8/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jackspeak": { - "version": "3.4.3", + "node_modules/c8/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "p-limit": "^3.0.2" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/json-parse-even-better-errors": { + "node_modules/c8/node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", + "node_modules/c8/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmaccess": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "@npmcli/arborist": "^8.0.1", - "@npmcli/installed-package-contents": "^3.0.0", - "binary-extensions": "^2.3.0", - "diff": "^5.1.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "tar": "^6.2.1" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.1", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.1", - "@npmcli/run-script": "^9.0.1", - "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "proc-log": "^5.0.0", - "read": "^4.0.0", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "walk-up-path": "^3.0.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.1", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmhook": { - "version": "11.0.0", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmorg": { - "version": "7.0.0", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.1", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/arborist": "^8.0.1", - "@npmcli/run-script": "^9.0.1", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmpublish": { - "version": "10.0.1", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ci-info": "^4.0.0", - "normalize-package-data": "^7.0.0", - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1", - "proc-log": "^5.0.0", - "semver": "^7.3.7", - "sigstore": "^3.0.0", - "ssri": "^12.0.0" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmsearch": { - "version": "8.0.0", + "node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-registry-fetch": "^18.0.1" + "escape-string-regexp": "5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmteam": { - "version": "7.0.0", + "node_modules/clean-stack/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmversion": { - "version": "7.0.0", + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.1", - "@npmcli/run-script": "^9.0.1", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.7" + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/lru-cache": { - "version": "10.4.3", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/make-fetch-happen": { - "version": "14.0.3", + "node_modules/cli-highlight/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", + "node_modules/cli-highlight/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass": { - "version": "7.1.2", + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "inBundle": true, "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-collect": { + "node_modules/cli-highlight/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "color-name": "~1.1.4" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=7.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.1", + "node_modules/cli-highlight/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" + "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "inBundle": true, "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "string-width": "^4.2.0" }, "engines": { - "node": ">=8" + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minizlib": { - "version": "3.0.2", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "inBundle": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "color-name": "~1.1.4" }, "engines": { - "node": ">=10" + "node": ">=7.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ms": { - "version": "2.1.3", + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "inBundle": true, "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/mute-stream": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp": { - "version": "11.2.0", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "tinyglobby": "^0.2.12", - "which": "^5.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "inBundle": true, "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=0.8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, "engines": { "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/nopt": { - "version": "8.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "color-name": "^2.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14.6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", + "node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" + "color-name": "^2.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-audit-report": { - "version": "6.0.0", + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.1.90" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-bundled": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.20.0 || >=14" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "4.0.0", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.2", + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-packlist": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { - "ignore-walk": "^7.0.0" + "mime-db": ">= 1.43.0 < 2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-pick-manifest": { - "version": "10.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", - "semver": "^7.3.5" + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-profile": { - "version": "11.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "ms": "2.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-registry-fetch": { - "version": "18.0.2", + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", "dependencies": { - "@npmcli/redact": "^3.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-user-validate": { - "version": "3.0.0", + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/p-map": { - "version": "7.0.3", + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, - "inBundle": true, + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "safe-buffer": "5.2.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0" + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/pacote": { - "version": "19.0.1", + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" + "compare-func": "^2.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/parse-conflict-json": { - "version": "4.0.0", + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", "dev": true, - "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" + "compare-func": "^2.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/path-key": { - "version": "3.1.1", + "node_modules/conventional-changelog-writer": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", + "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", "dev": true, - "inBundle": true, "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", + "node_modules/conventional-changelog-writer/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.0", + "node_modules/conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/proc-log": { + "node_modules/conventional-commits-parser": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=16" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/proggy": { - "version": "3.0.0", + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", + "node": ">=12" + }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.2", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "license": "Apache-2.0", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promzard": { - "version": "2.0.0", - "dev": true, - "inBundle": true, + "node_modules/convict/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "license": "ISC", - "dependencies": { - "read": "^4.0.0" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read": { - "version": "4.1.0", + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { - "mute-stream": "^2.0.0" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.10" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read-cmd-shim": { - "version": "5.0.0", + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read-package-json-fast": { - "version": "4.0.0", + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "jiti": "^2.6.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "dev": true, - "inBundle": true, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, "engines": { - "node": ">= 4" + "node": ">=12.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "inBundle": true, "license": "MIT", - "optional": true - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "type-fest": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, - "inBundle": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@babel/runtime": "^7.21.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" + "ms": "^2.1.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { - "version": "2.0.0", + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.1.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" + "node": ">=10" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.1.1", + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=4.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "inBundle": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "node": ">=0.4.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/socks": { - "version": "2.8.5", - "dev": true, - "inBundle": true, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "node": ">= 0.8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.5", - "dev": true, - "inBundle": true, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, "engines": { - "node": ">= 14" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "license": "ISC", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "asap": "^2.0.0", + "wrappy": "1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.5.0", - "dev": true, - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-expression-parse": { - "version": "4.0.0", + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.21", - "dev": true, - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ssri": { - "version": "12.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/string-width": { - "version": "4.2.3", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "path-type": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "is-obj": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/dreamopt": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.8.0.tgz", + "integrity": "sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==", "dev": true, - "inBundle": true, - "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "wordwrap": ">=0.0.2" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "inBundle": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar": { - "version": "6.2.1", + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "safe-buffer": "~5.1.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", "dev": true, - "inBundle": true, + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, "engines": { - "node": ">= 8" + "node": ">= 0.8" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "once": "^1.4.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/text-table": { - "version": "0.2.0", + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, - "inBundle": true, - "license": "MIT" + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", + "node_modules/env-ci": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", "dev": true, - "inBundle": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby": { - "version": "0.2.14", + "node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", + "node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, - "inBundle": true, "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" + "engines": { + "node": ">=16" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", + "node_modules/env-ci/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/env-ci/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tuf-js": { - "version": "3.0.1", + "node_modules/env-ci/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" + "path-key": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { - "version": "3.0.1", + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" + "mimic-fn": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/unique-filename": { + "node_modules/env-ci/node_modules/path-key": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/unique-slug": { - "version": "5.0.0", + "node_modules/env-ci/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "inBundle": true, "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", + "node_modules/env-ci/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.1", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/which": { - "version": "5.0.0", + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, - "inBundle": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "is-arrayish": "^0.2.1" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "ISC", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { - "node": ">=16" + "node": ">= 0.4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", - "dev": true, - "inBundle": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "dev": true, - "inBundle": true, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "inBundle": true, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=6" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "inBundle": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, - "inBundle": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/write-file-atomic": { - "version": "6.0.0", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/@semantic-release/npm/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", "dev": true, "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, "engines": { - "node": ">=12" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=14" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@semantic-release/npm/node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", "dependencies": { - "is-stream": "^3.0.0", - "temp-dir": "^3.0.0", - "type-fest": "^2.12.2", - "unique-string": "^3.0.0" - }, - "engines": { - "node": ">=14.16" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@semantic-release/npm/node_modules/tempy/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "color-convert": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12.20" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", - "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "get-stream": "^7.0.0", - "import-from-esm": "^2.0.0", - "into-stream": "^7.0.0", - "lodash-es": "^4.17.21", - "read-package-up": "^11.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" + "node": ">=7.0.0" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", - "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-writer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", - "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "conventional-commits-filter": "^5.0.0", - "handlebars": "^4.7.7", - "meow": "^13.0.0", - "semver": "^7.5.2" - }, - "bin": { - "conventional-changelog-writer": "dist/cli/index.js" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", - "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=16" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "type-detect": "4.0.8" + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-3-Clause", + "license": "BSD-2-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" } }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" } }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/@so-ric/colorspace": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", - "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", - "dependencies": { - "color": "^5.0.2", - "text-hex": "1.0.x" + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "bare-events": "^2.7.0" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@types/conventional-commits-parser": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", - "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "node_modules/express-openapi-validator": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.0.tgz", + "integrity": "sha512-gNaMgDb1cAT8QKcuh9WrED9p3mqi/V7yocNrvnE1fOz7e8p8JkbYaTUcOB4VsZKerz/X+Sey7ptTGF5FwsXh8Q==", "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "@apidevtools/json-schema-ref-parser": "^14.0.3", + "@types/multer": "^1.4.13", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "json-schema-traverse": "^1.0.0", + "lodash.clonedeep": "^4.5.0", + "lodash.get": "^4.4.2", + "media-typer": "^1.1.0", + "multer": "^2.0.2", + "ono": "^7.1.3", + "path-to-regexp": "^8.2.0", + "qs": "^6.14.0" + }, + "peerDependencies": { + "express": "*" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, + "node_modules/express-openapi-validator/node_modules/@apidevtools/json-schema-ref-parser": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", + "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + }, + "peerDependencies": { + "@types/json-schema": "^7.0.15" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, + "node_modules/express-openapi-validator/node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-session": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.1.0", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "@types/ms": "*", - "@types/node": "*" + "ms": "2.0.0" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/@types/multer": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", - "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "node_modules/express/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "@types/express": "*" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "ms": "2.0.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" + "node_modules/express/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" + "node_modules/express/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "node_modules/express/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "@types/node": "*" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "node_modules/express/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT" }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/@types/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/webidl-conversions": "*" - } + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } + "license": "MIT" }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "is-unicode-supported": "^2.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/figures/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node": ">=16.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "ms": "2.0.0" } }, - "node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { - "ajv": "^8.0.0" + "semver": "^6.0.0" }, - "peerDependencies": { - "ajv": "^8.0.0" + "engines": { + "node": ">=8" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", "dev": true, "license": "MIT", "dependencies": { - "environment": "^1.0.0" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" }, "engines": { "node": ">=18" @@ -4767,430 +4809,414 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-styles/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=7.0.0" + "node": ">=16" } }, - "node_modules/ansi-styles/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" + "node_modules/fn-args": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", + "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } }, - "node_modules/argv-formatter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", - "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", - "dev": true, - "license": "MIT" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT" - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/async-await-retry": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/async-await-retry/-/async-await-retry-2.1.0.tgz", - "integrity": "sha512-eP0cVR8SfTussawaGL1edKe6aQJiVo2A8+TFBhpg3GKMHcn3FvAT/CfdaPtXdbsLsca/L7qwhi7e8D7SDFnoNQ==", - "license": "MIT", - "bin": { - "async-await-retry": "index.js" + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=7.6.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/async-mutex": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", - "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/basic-auth/node_modules/safe-buffer": { + "node_modules/from2/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } }, - "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=12" } }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", "dev": true, "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "ISC" - }, - "node_modules/bson": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", - "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==", - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "engines": { - "node": ">=20.19.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "streamsearch": "^1.1.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=10.16.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/c8": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", - "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^1.0.1", - "@istanbuljs/schema": "^0.1.3", - "find-up": "^5.0.0", - "foreground-child": "^3.1.1", - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.1.6", - "test-exclude": "^7.0.1", - "v8-to-istanbul": "^9.0.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "c8": "bin/c8.js" + "pump": "^3.0.0" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "monocart-coverage-reports": "^2" + "node": ">=8" }, - "peerDependenciesMeta": { - "monocart-coverage-reports": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/c8/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/git-log-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", + "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "0.6.8" } }, - "node_modules/c8/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "git-raw-commits": "cli.mjs" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=16" } }, - "node_modules/c8/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=14" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/c8/node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/c8/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "balanced-match": "^1.0.0" } }, - "node_modules/c8/node_modules/minimatch": { + "node_modules/glob/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", @@ -5206,2913 +5232,2800 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/c8/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "ini": "4.1.1" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/c8/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/c8/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/c8/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/c8/node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=18" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/c8/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { + "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "bin": { + "he": "bin/he" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "dev": true, + "license": "MIT" + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.0.0" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hook-std": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", + "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 0.8" }, "funding": { - "url": "https://paulmillr.com/funding/" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/clean-stack": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", - "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "5.0.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/clean-stack/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", + "bin": { + "husky": "bin.js" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/typicode" } }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "license": "MIT", "dependencies": { - "string-width": "^4.2.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "10.* || >= 12.*" + "node": ">=0.10.0" }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 4" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=4" } }, - "node_modules/color": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", - "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "node_modules/import-from-esm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", + "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^3.1.3", - "color-string": "^2.1.3" + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=18.20" } }, - "node_modules/color-convert": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", - "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, "license": "MIT", - "dependencies": { - "color-name": "^2.0.0" - }, - "engines": { - "node": ">=14.6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">=0.8.19" } }, - "node_modules/color-string": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", - "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, "license": "MIT", - "dependencies": { - "color-name": "^2.0.0" + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.1.90" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">= 0.10" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=0.12.0" } }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/concat-stream": { + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" }, "engines": { - "node": ">= 0.6" + "node": "^18.17 || >=20.6.1" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "compare-func": "^2.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=10" } }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", - "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "compare-func": "^2.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">=16" + "node": ">=8" } }, - "node_modules/conventional-commits-filter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.6.0" } }, - "node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.mjs" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=16" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT" - }, - "node_modules/convict": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", - "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "lodash.clonedeep": "^4.5.0", - "yargs-parser": "^20.2.7" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/convict/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", "engines": { "node": ">=10" - } - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.10" + "node": ">=7.0.0" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/cosmiconfig-typescript-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", - "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "jiti": "^2.6.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=v18" + "node": ">=8" }, - "peerDependencies": { - "@types/node": "*", - "cosmiconfig": ">=9", - "typescript": ">=5" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { - "luxon": "^3.2.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 8" + "node": ">=7.0.0" } }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.0" + "node": ">=8" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=4.0.0" + "node": ">=7.0.0" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=0.4.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "license": "ISC", "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.3.1" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/dreamopt": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.8.0.tgz", - "integrity": "sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "dependencies": { - "wordwrap": ">=0.0.2" - }, - "engines": { - "node": ">=0.4.0" - } + "license": "MIT" }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">= 0.4" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", + "node_modules/json-diff": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-1.0.6.tgz", + "integrity": "sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==", + "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "@ewoudenberg/difflib": "0.1.0", + "colors": "^1.4.0", + "dreamopt": "~0.8.0" + }, + "bin": { + "json-diff": "bin/json-diff.js" + }, + "engines": { + "node": "*" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, "license": "MIT" }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { - "once": "^1.4.0" + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" }, - "node_modules/env-ci": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", - "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, - "license": "MIT", + "license": "(MIT OR Apache-2.0)", "dependencies": { - "execa": "^8.0.0", - "java-properties": "^1.0.2" + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" }, "engines": { - "node": "^18.17 || >=20.6.1" + "node": "*" } }, - "node_modules/env-ci/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/env-ci/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/env-ci/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/env-ci/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/env-ci/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14" } }, - "node_modules/env-ci/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "license": "MIT", "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" } }, - "node_modules/env-ci/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/env-ci/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, + "node_modules/jwks-rsa/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/env-ci/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node_modules/jwks-rsa/node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" } }, - "node_modules/env-ci/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12.0.0" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" + "json-buffer": "3.0.1" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8.0" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=4" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } + "license": "MIT" }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } + "license": "MIT" }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "license": "MIT" }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "license": "MIT" }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "node_modules/lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==", + "license": "MIT" }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "license": "MIT" }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, - "node_modules/eslint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, - "node_modules/eslint/node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, - "node_modules/eslint/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "node_modules/lodash.last": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", + "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", + "license": "MIT" }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } + "license": "MIT" }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } + "license": "MIT" }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true, + "license": "MIT" }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } + "license": "MIT" }, - "node_modules/execa": { + "node_modules/lodash.values": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", + "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==", + "license": "MIT" + }, + "node_modules/log-symbols": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.10.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-openapi-validator": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.0.tgz", - "integrity": "sha512-gNaMgDb1cAT8QKcuh9WrED9p3mqi/V7yocNrvnE1fOz7e8p8JkbYaTUcOB4VsZKerz/X+Sey7ptTGF5FwsXh8Q==", - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^14.0.3", - "@types/multer": "^1.4.13", - "ajv": "^8.17.1", - "ajv-draft-04": "^1.0.0", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "json-schema-traverse": "^1.0.0", - "lodash.clonedeep": "^4.5.0", - "lodash.get": "^4.4.2", - "media-typer": "^1.1.0", - "multer": "^2.0.2", - "ono": "^7.1.3", - "path-to-regexp": "^8.2.0", - "qs": "^6.14.0" - }, - "peerDependencies": { - "express": "*" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/express-openapi-validator/node_modules/@apidevtools/json-schema-ref-parser": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", - "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { - "js-yaml": "^4.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 20" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" + "node": ">=10" }, - "peerDependencies": { - "@types/json-schema": "^7.0.15" - } - }, - "node_modules/express-openapi-validator/node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/express-session": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", - "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.1.0", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express-session/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">=7.0.0" } }, - "node_modules/express-session/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 12.0.0" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.1.90" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", "dependencies": { - "ms": "2.0.0" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" } }, - "node_modules/express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=12" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "node_modules/make-asynchronous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", + "integrity": "sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "1.2.0" }, "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/express/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, - "license": "MIT" + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "license": "MIT" }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, "engines": { - "node": ">=18" + "node": ">=16.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/figures/node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.6" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "node_modules/migrate-mongo": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-12.1.3.tgz", + "integrity": "sha512-UHXkNVVNKaPSXAHvzcCgvc41FpSqUxTlaBNSl+DpGBDxbIZ1B85ql2k2rphXSsh8zKhHS6qHrboLabYuuu/8Eg==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "cli-table3": "^0.6.1", + "commander": "^9.1.0", + "date-fns": "^2.28.0", + "fn-args": "^5.0.0", + "fs-extra": "^10.0.1", + "lodash.filter": "^4.6.0", + "lodash.find": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.isempty": "^4.4.0", + "lodash.last": "^3.0.0", + "lodash.values": "^4.3.0", + "p-each-series": "^2.2.0" + }, + "bin": { + "migrate-mongo": "bin/migrate-mongo.js" }, "engines": { "node": ">=8" + }, + "peerDependencies": { + "mongodb": "^4.4.1 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "bin": { + "mime": "bin/cli.js" }, "engines": { - "node": ">= 0.8" + "node": ">=16" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/find-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", - "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "locate-path": "^7.2.0", - "path-exists": "^5.0.0", - "unicorn-magic": "^0.1.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, - "node_modules/find-up-simple": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", - "dev": true, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", - "engines": { - "node": ">=18" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-versions": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", - "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "MIT", - "dependencies": { - "semver-regex": "^4.0.5", - "super-regex": "^1.0.0" - }, + "license": "ISC", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, "bin": { - "flat": "cli.js" + "mkdirp": "bin/cmd.js" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": ">=16" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/fn-args": { + "node_modules/mocha/node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", - "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", - "license": "MIT" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=10" }, "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/mocha/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=12" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/function-timeout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", - "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", + "node_modules/mongodb": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", + "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.0.0", + "mongodb-connection-string-url": "^7.0.0" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "node_modules/mongodb-connection-string-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", + "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=20.19.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/mongodb-memory-server": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.3.tgz", + "integrity": "sha512-CDZvFisXvGIigsIw5gqH6r9NI/zxGa/uRdutgUL/isuJh+inj0YXb7Ykw6oFMFzqgTJWb7x0I5DpzrqCstBWpg==", + "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "mongodb-memory-server-core": "10.4.3", + "tslib": "^2.8.1" }, "engines": { - "node": ">= 0.4" + "node": ">=16.20.1" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/mongodb-memory-server-core": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.3.tgz", + "integrity": "sha512-IPjlw73IoSYopnqBibQKxmAXMbOEPf5uGAOsBcaUiNH/TOI7V19WO+K7n5KYtnQ9FqzLGLpvwCGuPOTBSg4s5Q==", "dev": true, "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "async-mutex": "^0.5.0", + "camelcase": "^6.3.0", + "debug": "^4.4.3", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.11", + "https-proxy-agent": "^7.0.6", + "mongodb": "^6.9.0", + "new-find-package-json": "^2.0.0", + "semver": "^7.7.3", + "tar-stream": "^3.1.7", + "tslib": "^2.8.1", + "yauzl": "^3.2.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.20.1" } }, - "node_modules/git-log-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", - "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", "dev": true, "license": "MIT", "dependencies": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "0.6.8" + "@types/webidl-conversions": "*" } }, - "node_modules/git-log-parser/node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "node_modules/mongodb-memory-server-core/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" } }, - "node_modules/git-log-parser/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", + "integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, - "node_modules/git-log-parser/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/git-log-parser/node_modules/split2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "through2": "~2.0.0" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/git-log-parser/node_modules/stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", - "dev": true, + "node_modules/mongoose": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.21.0.tgz", + "integrity": "sha512-dW2U01gN8EVQT5KAO5AkzjbqWc8A/CsEq15jOzq/M9ISpy8jw3iq7W9ZP135h9zykFOMt3AMxq4+anvt2YNJgw==", "license": "MIT", "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" } }, - "node_modules/git-log-parser/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "@types/webidl-conversions": "*" } }, - "node_modules/git-log-parser/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "node_modules/mongoose/node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" } }, - "node_modules/git-raw-commits": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "dev": true, - "license": "MIT", + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", "dependencies": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.mjs" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" }, "engines": { - "node": ">=16" + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "dev": true, - "license": "ISC", + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.8.0" } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "ms": "2.0.0" } }, - "node_modules/glob/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" + "ee-first": "1.1.1" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.8" } }, - "node_modules/glob/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4.0.0" } }, - "node_modules/glob/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "debug": "4.x" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.0.0" } }, - "node_modules/glob/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", "license": "MIT", "dependencies": { - "ini": "4.1.1" + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 10.16.0" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.6" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { - "handlebars": "bin/handlebars" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": "^18 || >=20" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/new-find-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "debug": "^4.3.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=12.22.0" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "clone": "2.x" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 8.0.0" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" + "node": ">=18" } }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "dev": true, - "license": "MIT" - }, - "node_modules/helmet": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", - "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "node_modules/node-schedule": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", + "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", "license": "MIT", + "dependencies": { + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + }, "engines": { - "node": ">=18.0.0" + "node": ">=6" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": "*" + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" } }, - "node_modules/hook-std": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", - "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { + "node_modules/normalize-package-data/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, "engines": { - "node": ">= 0.8" + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/npm": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], "dev": true, - "license": "MIT", + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.1", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.1", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.1", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" }, "engines": { - "node": ">= 14" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.12.0" + "node": ">=8" } }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=12" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } + "inBundle": true, + "license": "MIT" }, - "node_modules/import-from-esm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", - "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "import-meta-resolve": "^4.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, - "engines": { - "node": ">=18.20" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", - "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -8120,2362 +8033,2476 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/index-to-position": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", - "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/into-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", - "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "ansi-regex": "^6.0.1" }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/into-stream/node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/into-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/into-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/into-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", + "minipass": "^7.0.4" + }, "engines": { - "node": ">= 0.10" + "node": ">=18.0.0" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "inBundle": true, + "license": "ISC" }, - "node_modules/is-fullwidth-code-point": { + "node_modules/npm/node_modules/@npmcli/agent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "is-extglob": "^2.1.1" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" }, "engines": { - "node": ">=0.10.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, "engines": { - "node": ">=0.12.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-text-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", - "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "text-extensions": "^2.0.0" + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/issue-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", - "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": "^18.17 || >=20.6.1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.2.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "ISC", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "which": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.1", "dev": true, - "license": "BlueOak-1.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "postcss-selector-parser": "^7.0.0" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jackspeak/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.1.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" }, "engines": { - "node": ">=12" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jackspeak/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", "dev": true, + "inBundle": true, "license": "MIT", + "optional": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=14" } }, - "node_modules/jackspeak/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/jackspeak/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jackspeak/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/jackspeak/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/java-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.3", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 0.6.0" + "node": ">= 14" } }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.2", "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.4.1", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/ci-info": { + "version": "4.2.0", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ip-regex": "^5.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=14" } }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 10" } }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 8" } }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "inBundle": true, "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, "bin": { - "js-yaml": "bin/js-yaml.js" + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-diff": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-1.0.6.tgz", - "integrity": "sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==", + "node_modules/npm/node_modules/debug": { + "version": "4.4.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@ewoudenberg/difflib": "0.1.0", - "colors": "^1.4.0", - "dreamopt": "~0.8.0" - }, - "bin": { - "json-diff": "bin/json-diff.js" + "ms": "^2.1.3" }, "engines": { - "node": "*" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "iconv-lite": "^0.6.2" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", "dev": true, - "engines": [ - "node >= 0.2.0" - ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.2", "dev": true, - "license": "(MIT OR Apache-2.0)", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": "*" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "license": "MIT", + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" + "minipass": "^7.0.3" }, "engines": { - "node": ">=12", - "npm": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jwks-rsa": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", - "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", - "license": "MIT", + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/express": "^4.17.20", - "@types/jsonwebtoken": "^9.0.4", - "debug": "^4.3.4", - "jose": "^4.15.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.2.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=14" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" }, - "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/jwks-rsa/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/jwks-rsa/node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=0.8.19" } }, - "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "license": "Apache-2.0", + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=12.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT" + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 12" } }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "cidr-regex": "^4.1.1" }, "engines": { - "node": ">=4" + "node": ">=14" } }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "@isaacs/cliui": "^8.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.capitalize": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", "dev": true, - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "license": "MIT" + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, "license": "MIT" }, - "node_modules/lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/lodash.find": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", - "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==", + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", - "license": "MIT" + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.last": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", - "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", - "license": "MIT" + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/lodash.uniqby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/lodash.upperfirst": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.1", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } }, - "node_modules/lodash.values": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", - "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==", - "license": "MIT" + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "node_modules/npm/node_modules/minizlib": { + "version": "3.0.2", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 18" } }, - "node_modules/logform/node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, "engines": { - "node": ">=0.1.90" + "node": ">=10" } }, - "node_modules/long-timeout": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", - "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "dev": true, + "inBundle": true, "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/lru-memoizer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", - "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "node_modules/npm/node_modules/node-gyp": { + "version": "11.2.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "6.0.0" - } - }, - "node_modules/luxon": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", - "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", - "license": "MIT", + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, "engines": { - "node": ">=12" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/make-asynchronous": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", - "integrity": "sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==", + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "p-event": "^6.0.0", - "type-fest": "^4.6.0", - "web-worker": "1.2.0" - }, + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "semver": "^7.5.3" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/marked": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": ">= 18" + "node": ">=18" } }, - "node_modules/marked-terminal": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", - "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "ansi-regex": "^6.1.0", - "chalk": "^5.4.1", - "cli-highlight": "^2.1.11", - "cli-table3": "^0.6.5", - "node-emoji": "^2.2.0", - "supports-hyperlinks": "^3.1.0" - }, + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "marked": ">=1 <16" + "node": ">=18" } }, - "node_modules/marked-terminal/node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "node_modules/npm/node_modules/nopt": { + "version": "8.1.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" + "abbrev": "^3.0.0" }, "bin": { - "highlight": "bin/highlight" + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", "dev": true, + "inBundle": true, "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/marked-terminal/node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "parse5": "^6.0.1" + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/marked-terminal/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", "dev": true, + "inBundle": true, "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/marked-terminal/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", "dev": true, + "inBundle": true, "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" - }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "node_modules/npm/node_modules/p-map": { + "version": "7.0.3", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=16.10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "BlueOak-1.0.0" }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", + "node_modules/npm/node_modules/pacote": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" }, "engines": { - "node": ">=8.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/migrate-mongo": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-12.1.3.tgz", - "integrity": "sha512-UHXkNVVNKaPSXAHvzcCgvc41FpSqUxTlaBNSl+DpGBDxbIZ1B85ql2k2rphXSsh8zKhHS6qHrboLabYuuu/8Eg==", + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "cli-table3": "^0.6.1", - "commander": "^9.1.0", - "date-fns": "^2.28.0", - "fn-args": "^5.0.0", - "fs-extra": "^10.0.1", - "lodash.filter": "^4.6.0", - "lodash.find": "^4.6.0", - "lodash.get": "^4.4.2", - "lodash.isempty": "^4.4.0", - "lodash.last": "^3.0.0", - "lodash.values": "^4.3.0", - "p-each-series": "^2.2.0" - }, - "bin": { - "migrate-mongo": "bin/migrate-mongo.js" - }, "engines": { "node": ">=8" - }, - "peerDependencies": { - "mongodb": "^4.4.1 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/migrate-mongo/node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "license": "MIT", + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@babel/runtime": "^7.21.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=0.11" + "node": ">=16 || 14 >=14.18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mime": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", - "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.0", "dev": true, - "funding": [ - "https://github.com/sponsors/broofa" - ], + "inBundle": true, "license": "MIT", - "bin": { - "mime": "bin/cli.js" + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=16" + "node": ">=4" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "err-code": "^2.0.2", + "retry": "^0.12.0" }, "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/mime-types/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/npm/node_modules/read": { + "version": "4.1.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "mute-stream": "^2.0.0" }, "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", "dev": true, + "inBundle": true, "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "minimist": "^1.2.6" + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mocha": { - "version": "11.7.5", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", - "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 4" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } + "optional": true }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/npm/node_modules/semver": { + "version": "7.7.2", "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", "dev": true, + "inBundle": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/p-limit": { + "node_modules/npm/node_modules/sigstore": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "yocto-queue": "^0.1.0" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { + "version": "3.1.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "p-limit": "^3.0.2" + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { + "version": "2.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { + "version": "3.1.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "has-flag": "^4.0.0" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mocha/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.5", "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/mongodb": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", - "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", - "license": "Apache-2.0", - "peer": true, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", "dependencies": { - "@mongodb-js/saslprep": "^1.3.0", - "bson": "^7.0.0", - "mongodb-connection-string-url": "^7.0.0" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.806.0", - "@mongodb-js/zstd": "^7.0.0", - "gcp-metadata": "^7.0.1", - "kerberos": "^7.0.0", - "mongodb-client-encryption": ">=7.0.0 <7.1.0", - "snappy": "^7.3.2", - "socks": "^2.8.6" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } + "node": ">= 14" } }, - "node_modules/mongodb-connection-string-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", - "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "@types/whatwg-url": "^13.0.0", - "whatwg-url": "^14.1.0" - }, - "engines": { - "node": ">=20.19.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/mongodb-memory-server": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.1.tgz", - "integrity": "sha512-XpCyV1e7QQ1lW28rgtXP4ZlX8ZfD/8z1ZGNxz2y3JrosLgDrNnYWvPjlgFj3JjboYUtlh1jF2Ez/rwsQA6cl0w==", + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", "dev": true, - "hasInstallScript": true, + "inBundle": true, "license": "MIT", "dependencies": { - "mongodb-memory-server-core": "10.4.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=16.20.1" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/mongodb-memory-server/node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@types/webidl-conversions": "*" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/mongodb-memory-server/node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.21", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": ">=16.20.1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mongodb-memory-server/node_modules/mongodb": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", - "integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==", + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "MIT", "dependencies": { - "@mongodb-js/saslprep": "^1.3.0", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.3.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } + "node": ">=8" } }, - "node_modules/mongodb-memory-server/node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "MIT", "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/mongodb-memory-server/node_modules/mongodb-memory-server-core": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.1.tgz", - "integrity": "sha512-YJdrEyF9hk64nfeoVDMP6IfTzK+gLZhrQqYyP6JJMsqo2LK5eF7JRZ4YPQDmt1re/JhItpiU+ypiZbIG1OsW5Q==", + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "async-mutex": "^0.5.0", - "camelcase": "^6.3.0", - "debug": "^4.4.3", - "find-cache-dir": "^3.3.2", - "follow-redirects": "^1.15.11", - "https-proxy-agent": "^7.0.6", - "mongodb": "^6.9.0", - "new-find-package-json": "^2.0.0", - "semver": "^7.7.3", - "tar-stream": "^3.1.7", - "tslib": "^2.8.1", - "yauzl": "^3.2.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=16.20.1" + "node": ">=8" } }, - "node_modules/mongoose": { - "version": "8.20.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.2.tgz", - "integrity": "sha512-U0TPupnqBOAI3p9H9qdShX8/nJUBylliRcHFKuhbewEkM7Y0qc9BbrQR9h4q6+1easoZqej7cq2Ee36AZ0gMzQ==", + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.20.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=16.20.1" + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mongoose/node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "license": "MIT", + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@types/webidl-conversions": "*" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/mongoose/node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", - "license": "Apache-2.0", + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { - "node": ">=16.20.1" + "node": ">= 8" } }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", - "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", - "license": "Apache-2.0", + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@mongodb-js/saslprep": "^1.3.0", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.2" + "yallist": "^4.0.0" }, "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.3.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } + "node": ">=8" } }, - "node_modules/mongoose/node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" } }, - "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "node_modules/npm/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.1.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 8" } }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", + "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "ms": "2.0.0" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.14", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">= 0.8" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">=4.0.0" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "debug": "4.x" - }, "engines": { - "node": ">=14.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" }, "engines": { - "node": ">= 10.16.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/multer/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/multer/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "unique-slug": "^5.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", - "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" + "imurmurhash": "^0.1.4" }, "engines": { - "node": "^18 || >=20" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.1", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/nerf-dart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC" }, - "node_modules/new-find-package-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", - "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", + "node_modules/npm/node_modules/which": { + "version": "5.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "debug": "^4.3.4" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=12.22.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-cache": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", - "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "clone": "2.x" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">= 8.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/node-schedule": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", - "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "cron-parser": "^4.2.0", - "long-timeout": "0.1.1", - "sorted-array-functions": "^1.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", - "deprecated": "Use uuid module instead", - "bin": { - "uuid": "bin/uuid" + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/normalize-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", - "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=14.16" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10860,6 +10887,23 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, "node_modules/parse5-query-domtree": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse5-query-domtree/-/parse5-query-domtree-1.0.2.tgz", @@ -10956,6 +11000,30 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -11207,9 +11275,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -11219,6 +11287,21 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", @@ -11289,9 +11372,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -11431,41 +11514,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-pkg/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/read-pkg/node_modules/parse-json": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", @@ -11861,9 +11909,9 @@ } }, "node_modules/send": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", - "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -11872,13 +11920,13 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -11899,22 +11947,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -11927,15 +11959,6 @@ "node": ">=4" } }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -11947,105 +11970,20 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-static/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", @@ -12433,6 +12371,50 @@ "node": ">= 0.8" } }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-combiner2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-combiner2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -12492,10 +12474,21 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12504,12 +12497,19 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12518,20 +12518,41 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/strip-ansi/node_modules/ansi-regex": { + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12589,20 +12610,20 @@ } }, "node_modules/superagent": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", - "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "license": "MIT", "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.4", + "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.2" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" @@ -12621,19 +12642,30 @@ } }, "node_modules/supertest": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", - "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^10.2.3" + "superagent": "^10.3.0" }, "engines": { "node": ">=14.18.0" } }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12665,9 +12697,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.30.3", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz", - "integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -12726,6 +12758,92 @@ "node": ">=14.16" } }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -12785,6 +12903,50 @@ "dev": true, "license": "MIT" }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", @@ -12811,6 +12973,54 @@ "node": ">=18" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -13016,16 +13226,6 @@ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, - "node_modules/undici/node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/unicode-emoji-modifier-base": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", @@ -13065,35 +13265,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/unique-string/node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unique-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -13119,6 +13290,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -13238,9 +13419,9 @@ } }, "node_modules/winston": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", - "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -13306,6 +13487,24 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -13325,6 +13524,103 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13340,6 +13636,16 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13391,16 +13697,6 @@ "node": ">=10" } }, - "node_modules/yargs/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yauzl": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", @@ -13442,9 +13738,9 @@ } }, "node_modules/zod": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" From 96133555f0c9730b40da1f0a109e2bb150ea9d56 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:21:42 -0500 Subject: [PATCH 156/370] refactor: migrate new endpoints to new /api/reports path --- app/api/definitions/openapi.yml | 19 ++-- .../paths/attack-objects-paths.yml | 42 -------- .../definitions/paths/relationships-paths.yml | 55 ----------- app/api/definitions/paths/reports-paths.yml | 97 +++++++++++++++++++ app/controllers/attack-objects-controller.js | 11 --- app/controllers/relationships-controller.js | 26 ----- app/controllers/reports-controller.js | 43 ++++++++ app/routes/attack-objects-routes.js | 8 -- app/routes/relationships-routes.js | 16 --- app/routes/reports-routes.js | 27 ++++++ app/services/reports-service.js | 60 ++++++++++++ app/services/stix/attack-objects-service.js | 10 -- app/services/stix/relationships-service.js | 15 --- 13 files changed, 236 insertions(+), 193 deletions(-) create mode 100644 app/api/definitions/paths/reports-paths.yml create mode 100644 app/controllers/reports-controller.js create mode 100644 app/routes/reports-routes.js create mode 100644 app/services/reports-service.js diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 05838053..8131b709 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -66,16 +66,14 @@ tags: description: 'Operations on user accounts' - name: 'Health Check' description: 'Operations on system status' + - name: 'Reports' + description: 'Operations for generating analytical reports on ATT&CK data' paths: # ATT&CK Objects /api/attack-objects: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects' - # Find objects with missing LinkById tags - /api/attack-objects/missing-linkbyid: - $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects~1missing-linkbyid' - # Generate next available ATT&CK ID /api/attack-objects/attack-id/next: $ref: 'paths/attack-objects-paths.yml#/paths/~1api~1attack-objects~1attack-id~1next' @@ -236,12 +234,6 @@ paths: /api/relationships: $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships' - /api/relationships/missing-linkbyid: - $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships~1missing-linkbyid' - - /api/relationships/parallel: - $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships~1parallel' - /api/relationships/{stixId}: $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships~1{stixId}' @@ -362,6 +354,13 @@ paths: /api/recent-activity: $ref: 'paths/recent-activity-paths.yml#/paths/~1api~1recent-activity' + # Reports + /api/reports/link-by-id/missing: + $ref: 'paths/reports-paths.yml#/paths/~1api~1reports~1link-by-id~1missing' + + /api/reports/parallel-relationships: + $ref: 'paths/reports-paths.yml#/paths/~1api~1reports~1parallel-relationships' + # Health Checks /api/health/ping: $ref: 'paths/health-paths.yml#/paths/~1api~1health~1ping' diff --git a/app/api/definitions/paths/attack-objects-paths.yml b/app/api/definitions/paths/attack-objects-paths.yml index 8575a862..ffb1e0e0 100644 --- a/app/api/definitions/paths/attack-objects-paths.yml +++ b/app/api/definitions/paths/attack-objects-paths.yml @@ -129,48 +129,6 @@ paths: - $ref: '../components/data-components.yml#/components/schemas/data-component' - $ref: '../components/data-sources.yml#/components/schemas/data-source' - /api/attack-objects/missing-linkbyid: - get: - summary: 'Get a list of ATT&CK objects with missing LinkById tags' - operationId: 'attack-object-get-missing-linkbyid' - description: | - This endpoint generates a list of ATT&CK objects that directly mention - attack.mitre.org in their descriptions, indicating that they are likely missing a - reference using the (LinkById: X####) pattern. - tags: - - 'ATT&CK Objects' - responses: - '200': - description: 'A list of ATT&CK Objects.' - content: - application/json: - schema: - type: array - items: - anyOf: - - $ref: '../components/collections.yml#/components/schemas/collection' - - $ref: '../components/groups.yml#/components/schemas/group' - - $ref: '../components/identities.yml#/components/schemas/identity' - - $ref: '../components/marking-definitions.yml#/components/schemas/marking-definition' - - $ref: '../components/matrices.yml#/components/schemas/matrix' - - $ref: '../components/mitigations.yml#/components/schemas/mitigation' - - $ref: '../components/software.yml#/components/schemas/software' - - $ref: '../components/tactics.yml#/components/schemas/tactic' - - $ref: '../components/techniques.yml#/components/schemas/technique' - - $ref: '../components/analytics.yml#/components/schemas/analytic' - - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' - - $ref: '../components/assets.yml#/components/schemas/asset' - - $ref: '../components/campaigns.yml#/components/schemas/campaign' - - $ref: '../components/data-components.yml#/components/schemas/data-component' - - $ref: '../components/data-sources.yml#/components/schemas/data-source' - '500': - description: 'Server error' - content: - text/plain: - schema: - type: string - example: 'Server error.' - /api/attack-objects/attack-id/next: get: summary: 'Get the next available ATT&CK ID for a STIX type' diff --git a/app/api/definitions/paths/relationships-paths.yml b/app/api/definitions/paths/relationships-paths.yml index dd13f458..85476bce 100644 --- a/app/api/definitions/paths/relationships-paths.yml +++ b/app/api/definitions/paths/relationships-paths.yml @@ -187,61 +187,6 @@ paths: '409': description: 'Duplicate `stix.id` and `stix.modified` properties. The relationship was not created.' - /api/relationships/missing-linkbyid: - get: - summary: 'Get a list of relationships with missing LinkById tags' - operationId: 'relationship-get-missing-linkbyid' - description: | - This endpoint generates a list of relationships that directly mention - attack.mitre.org in their descriptions, indicating that they are likely missing a - reference using the (LinkById: X####) pattern. - tags: - - 'Relationships' - responses: - '200': - description: 'A list of relationships.' - content: - application/json: - schema: - type: array - items: - $ref: '../components/relationships.yml#/components/schemas/relationship' - '500': - description: 'Server error' - content: - text/plain: - schema: - type: string - example: 'Server error.' - - /api/relationships/parallel: - get: - summary: 'Get a list of relationships with matching sources, targets, and relationship types.' - operationId: 'relationship-get-parallel' - description: | - This endpoint generates a map of lists of parallel relationships. Within each list, - the members share the same source_ref, target_ref, and relationship type values. - tags: - - 'Relationships' - responses: - '200': - description: 'A map of lists of parallel relationships.' - content: - application/json: - schema: - type: object - additionalProperties: - type: array - items: - $ref: '../components/relationships.yml#/components/schemas/relationship' - '500': - description: 'Server error' - content: - text/plain: - schema: - type: string - example: 'Server error.' - /api/relationships/{stixId}: get: summary: 'Get one or more versions of a relationship' diff --git a/app/api/definitions/paths/reports-paths.yml b/app/api/definitions/paths/reports-paths.yml new file mode 100644 index 00000000..bff1f68d --- /dev/null +++ b/app/api/definitions/paths/reports-paths.yml @@ -0,0 +1,97 @@ +paths: + /api/reports/link-by-id/missing: + get: + summary: 'Get objects with missing LinkById references' + operationId: 'reports-get-missing-linkbyid' + description: | + This endpoint generates a list of ATT&CK objects and/or relationships that directly + mention attack.mitre.org in their descriptions, indicating that they are likely missing + a reference using the (LinkById: X####) pattern. + tags: + - 'Reports' + parameters: + - name: type + in: query + description: | + Filter results by STIX type. If not specified, returns all types including relationships. + Use 'relationship' to get only relationships, or use any ATT&CK object type + (e.g., 'attack-pattern', 'x-mitre-tactic', 'intrusion-set', etc.) to filter by that type. + schema: + type: string + enum: + - relationship + - attack-pattern + - x-mitre-tactic + - intrusion-set + - malware + - tool + - course-of-action + - x-mitre-data-source + - x-mitre-data-component + - x-mitre-asset + - campaign + - x-mitre-matrix + - x-mitre-detection-strategy + - x-mitre-analytic + example: 'attack-pattern' + responses: + '200': + description: 'A list of objects with missing LinkById references.' + content: + application/json: + schema: + type: array + items: + anyOf: + - $ref: '../components/relationships.yml#/components/schemas/relationship' + - $ref: '../components/collections.yml#/components/schemas/collection' + - $ref: '../components/groups.yml#/components/schemas/group' + - $ref: '../components/identities.yml#/components/schemas/identity' + - $ref: '../components/marking-definitions.yml#/components/schemas/marking-definition' + - $ref: '../components/matrices.yml#/components/schemas/matrix' + - $ref: '../components/mitigations.yml#/components/schemas/mitigation' + - $ref: '../components/software.yml#/components/schemas/software' + - $ref: '../components/tactics.yml#/components/schemas/tactic' + - $ref: '../components/techniques.yml#/components/schemas/technique' + - $ref: '../components/analytics.yml#/components/schemas/analytic' + - $ref: '../components/detection-strategies.yml#/components/schemas/detection-strategy' + - $ref: '../components/assets.yml#/components/schemas/asset' + - $ref: '../components/campaigns.yml#/components/schemas/campaign' + - $ref: '../components/data-components.yml#/components/schemas/data-component' + - $ref: '../components/data-sources.yml#/components/schemas/data-source' + '500': + description: 'Server error' + content: + text/plain: + schema: + type: string + example: 'Unable to get objects with missing LinkById. Server error.' + + /api/reports/parallel-relationships: + get: + summary: 'Get parallel relationships' + operationId: 'reports-get-parallel-relationships' + description: | + This endpoint generates a map of lists of parallel relationships. Within each list, + the members share the same source_ref, target_ref, and relationship_type values. + This is useful for identifying potentially duplicate relationships. + tags: + - 'Reports' + responses: + '200': + description: 'A map of relationship keys to arrays of parallel relationships.' + content: + application/json: + schema: + type: object + additionalProperties: + type: array + items: + $ref: '../components/relationships.yml#/components/schemas/relationship' + '500': + description: 'Server error' + content: + text/plain: + schema: + type: string + example: 'Unable to get parallel relationships. Server error.' diff --git a/app/controllers/attack-objects-controller.js b/app/controllers/attack-objects-controller.js index 77541232..263b72de 100644 --- a/app/controllers/attack-objects-controller.js +++ b/app/controllers/attack-objects-controller.js @@ -35,17 +35,6 @@ exports.retrieveAll = async function (req, res) { } }; -exports.getObjectsMissingLinkById = async function (_, res) { - try { - const results = await attackObjectsService.retrieveAllWithAttackURLInDescription(); - logger.debug(`Success: Retrieved ${results.length} ATT&CK object(s)`); - return res.status(200).send(results); - } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get ATT&CK objects. Server error.'); - } -}; - exports.getNextAttackId = async function (req, res) { // Validate required query parameter if (!req.query.type) { diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index 0a9a0f8f..5ce19c77 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -96,32 +96,6 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.getRelationshipsMissingLinkById = async function (_, res) { - try { - const results = await relationshipsService.retrieveAllWithAttackURLInDescription(); - logger.debug(`Success: Retrieved ${results.length} relationship(s)`); - return res.status(200).send(results); - } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get relationships. Server error.'); - } -}; - -exports.getParallelRelationships = async function (_, res) { - try { - const results = await relationshipsService.getParallelRelationships(); - // console.log("parallel relationships:"); - // console.log(results); - logger.debug( - `Success: Retrieved ${Object.keys(results).length} set(s) of parallel relationship(s)`, - ); - return res.status(200).send(results); - } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get relationships. Server error.'); - } -}; - exports.create = async function (req, res) { // Get the data from the request const relationshipData = req.body; diff --git a/app/controllers/reports-controller.js b/app/controllers/reports-controller.js new file mode 100644 index 00000000..b930dae9 --- /dev/null +++ b/app/controllers/reports-controller.js @@ -0,0 +1,43 @@ +'use strict'; + +const reportsService = require('../services/reports-service'); +const logger = require('../lib/logger'); + +/** + * Handler for GET /api/reports/link-by-id/missing + * Retrieves objects that contain "attack.mitre.org" in their description. + * @param {Object} req - Express request object + * @param {Object} res - Express response object + */ +exports.getMissingLinkById = async function (req, res) { + const options = { + type: req.query.type, + }; + + try { + const results = await reportsService.getMissingLinkById(options); + logger.debug(`Success: Retrieved ${results.length} object(s) with missing LinkById`); + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get objects with missing LinkById. Server error.'); + } +}; + +/** + * Handler for GET /api/reports/parallel-relationships + * Retrieves parallel relationships (same source_ref, target_ref, and relationship_type). + * @param {Object} req - Express request object + * @param {Object} res - Express response object + */ +exports.getParallelRelationships = async function (req, res) { + try { + const results = await reportsService.getParallelRelationships(); + logger.debug(`Success: Retrieved ${results.size} set(s) of parallel relationship(s)`); + // Convert Map to object for JSON serialization + return res.status(200).send(Object.fromEntries(results)); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get parallel relationships. Server error.'); + } +}; diff --git a/app/routes/attack-objects-routes.js b/app/routes/attack-objects-routes.js index 3d11299c..91b04a62 100644 --- a/app/routes/attack-objects-routes.js +++ b/app/routes/attack-objects-routes.js @@ -16,14 +16,6 @@ router attackObjectsController.retrieveAll, ); -router - .route('/attack-objects/missing-linkbyid') - .get( - authn.authenticate, - authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), - attackObjectsController.getObjectsMissingLinkById, - ); - router .route('/attack-objects/attack-id/next') .get( diff --git a/app/routes/relationships-routes.js b/app/routes/relationships-routes.js index 83c90e1b..341c0644 100644 --- a/app/routes/relationships-routes.js +++ b/app/routes/relationships-routes.js @@ -21,22 +21,6 @@ router relationshipsController.create, ); -router - .route('/relationships/missing-linkbyid') - .get( - authn.authenticate, - authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), - relationshipsController.getRelationshipsMissingLinkById, - ); - -router - .route('/relationships/parallel') - .get( - authn.authenticate, - authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), - relationshipsController.getParallelRelationships, - ); - router .route('/relationships/:stixId') .get( diff --git a/app/routes/reports-routes.js b/app/routes/reports-routes.js new file mode 100644 index 00000000..2478652b --- /dev/null +++ b/app/routes/reports-routes.js @@ -0,0 +1,27 @@ +'use strict'; + +const express = require('express'); + +const reportsController = require('../controllers/reports-controller'); +const authn = require('../lib/authn-middleware'); +const authz = require('../lib/authz-middleware'); + +const router = express.Router(); + +router + .route('/reports/link-by-id/missing') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + reportsController.getMissingLinkById, + ); + +router + .route('/reports/parallel-relationships') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + reportsController.getParallelRelationships, + ); + +module.exports = router; diff --git a/app/services/reports-service.js b/app/services/reports-service.js new file mode 100644 index 00000000..0774a786 --- /dev/null +++ b/app/services/reports-service.js @@ -0,0 +1,60 @@ +'use strict'; + +const attackObjectsRepository = require('../repository/attack-objects-repository'); +const relationshipsRepository = require('../repository/relationships-repository'); +const identitiesService = require('./stix/identities-service'); + +/** + * Service for generating reports on ATT&CK objects and relationships. + * These are read-only analytical queries that identify potential data quality issues. + */ +class ReportsService { + /** + * Retrieves all objects (ATT&CK objects and/or relationships) that contain + * "attack.mitre.org" in their description, indicating a likely missing LinkById reference. + * @param {Object} options - Query options + * @param {string} [options.type] - Filter by STIX type (e.g., 'relationship', 'attack-pattern') + * @returns {Promise} Array of objects with attack.mitre.org in description + */ + async getMissingLinkById(options = {}) { + const results = []; + + // If type is 'relationship' or not specified, include relationships + if (!options.type || options.type === 'relationship') { + const relationships = await relationshipsRepository.retrieveAllWithAttackURLInDescription(); + await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(relationships); + results.push(...relationships); + } + + // If type is not 'relationship' or not specified, include attack objects + if (!options.type || options.type !== 'relationship') { + const attackObjects = await attackObjectsRepository.retrieveAllWithAttackURLInDescription(); + // If a specific type is requested, filter attack objects by type + const filteredObjects = options.type + ? attackObjects.filter((obj) => obj.stix?.type === options.type) + : attackObjects; + await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(filteredObjects); + results.push(...filteredObjects); + } + + return results; + } + + /** + * Retrieves parallel relationships - relationships that share the same source_ref, + * target_ref, and relationship_type. + * @returns {Promise} Map of relationship keys to arrays of parallel relationships + */ + async getParallelRelationships() { + const relationshipMap = await relationshipsRepository.retrieveParallelRelationships(); + + // Add identity information to each relationship in the map + for (const relationships of relationshipMap.values()) { + await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(relationships); + } + + return relationshipMap; + } +} + +module.exports = new ReportsService(); diff --git a/app/services/stix/attack-objects-service.js b/app/services/stix/attack-objects-service.js index 717cfa03..f08f073c 100644 --- a/app/services/stix/attack-objects-service.js +++ b/app/services/stix/attack-objects-service.js @@ -50,16 +50,6 @@ class AttackObjectsService extends BaseService { return documents; } - async retrieveAllWithAttackURLInDescription() { - // Get attack objects from repository - let documents = await this.repository.retrieveAllWithAttackURLInDescription(); - - // Add identities - await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(documents); - - return documents; - } - /** * Override of base class retrieveVersionById() because: * 1. Adds special handling for relationships diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index 9f1f1e7d..68b356c6 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -98,21 +98,6 @@ class RelationshipsService extends BaseService { return results; } } - - async retrieveAllWithAttackURLInDescription() { - let documents = await this.repository.retrieveAllWithAttackURLInDescription(); - await this.addCreatedByAndModifiedByIdentitiesToAll(documents); - - return documents; - } - - async getParallelRelationships() { - let relationship_map = await this.repository.retrieveParallelRelationships(); - relationship_map.forEach(async (value) => { - await this.addCreatedByAndModifiedByIdentitiesToAll(value); - }); - return relationship_map; - } } // Default export From 0b85a2a8b408b930d095eed5f0d45bc2e45e9c4e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:45:53 -0500 Subject: [PATCH 157/370] fix: update migrate-mongo usage for v14 async API - Await Promise exports (database, status, up) required in v14+ - Remove deprecated useNewUrlParser option unsupported in MongoDB driver 4.x+ --- app/lib/migration/migrate-database.js | 13 ++- app/lib/migration/migration-config.js | 4 - package-lock.json | 125 +++----------------------- package.json | 2 +- 4 files changed, 25 insertions(+), 119 deletions(-) diff --git a/app/lib/migration/migrate-database.js b/app/lib/migration/migrate-database.js index 7f4b1c85..0960be84 100644 --- a/app/lib/migration/migrate-database.js +++ b/app/lib/migration/migrate-database.js @@ -9,14 +9,21 @@ exports.migrateDatabase = async function () { file: './app/lib/migration/migration-config.js', }; - const { db, client } = await migrateMongo.database.connect(); - const migrationStatus = await migrateMongo.status(db); + // In migrate-mongo v14+, exports are Promises that must be awaited + const [database, status, up] = await Promise.all([ + migrateMongo.database, + migrateMongo.status, + migrateMongo.up, + ]); + + const { db, client } = await database.connect(); + const migrationStatus = await status(db); const actionsPending = Boolean(migrationStatus.find((elem) => elem.appliedAt === 'PENDING')); if (actionsPending) { if (config.database.migration.enable) { logger.info('Starting database migration...'); - const appliedActions = await migrateMongo.up(db, client); + const appliedActions = await up(db, client); for (const action of appliedActions) { logger.info(`Applied migration action: ${action}`); } diff --git a/app/lib/migration/migration-config.js b/app/lib/migration/migration-config.js index bbc9b8d7..76137d41 100644 --- a/app/lib/migration/migration-config.js +++ b/app/lib/migration/migration-config.js @@ -5,10 +5,6 @@ const config = require('../../config/config'); module.exports = { mongodb: { url: config.database.url, - - options: { - useNewUrlParser: true, - }, }, // The migrations dir, can be an relative or absolute path. Only edit this when really necessary. diff --git a/package-lock.json b/package-lock.json index 32f29013..73effbc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "jwks-rsa": "^3.2.0", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", - "migrate-mongo": "^12.1.3", + "migrate-mongo": "^14.0.7", "mongoose": "^8.15.1", "morgan": "^1.10.1", "nanoid": "^5.1.5", @@ -147,15 +147,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", @@ -5551,12 +5542,12 @@ } }, "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">=20" } }, "node_modules/commondir": { @@ -7344,15 +7335,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fn-args": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", - "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -7430,20 +7412,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7796,6 +7764,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/handlebars": { @@ -8835,6 +8804,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -9121,18 +9091,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", - "license": "MIT" - }, - "node_modules/lodash.find": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", - "integrity": "sha512-yaRZoAV3Xq28F1iafWN1+a0rflOej93l1DQUejs3SZ41h2O9UJBoS9aueGjPDgAl4B6tPC0NuuchLKaDQQ3Isg==", - "license": "MIT" - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -9152,12 +9110,6 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, - "node_modules/lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", - "license": "MIT" - }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -9189,12 +9141,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.last": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", - "integrity": "sha512-14mq7rSkCxG4XMy9lF2FbIOqqgF0aH0NfPuQ3LPR3vIh0kHnUvIYP70dqa1Hf47zyXfQ8FzAg0MYOQeSuE1R7A==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -9250,12 +9196,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.values": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", - "integrity": "sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==", - "license": "MIT" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -9631,50 +9571,24 @@ } }, "node_modules/migrate-mongo": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-12.1.3.tgz", - "integrity": "sha512-UHXkNVVNKaPSXAHvzcCgvc41FpSqUxTlaBNSl+DpGBDxbIZ1B85ql2k2rphXSsh8zKhHS6qHrboLabYuuu/8Eg==", + "version": "14.0.7", + "resolved": "https://registry.npmjs.org/migrate-mongo/-/migrate-mongo-14.0.7.tgz", + "integrity": "sha512-+p7XfJDNaXPTHeo7v/ldYmVLMy8xYda0KMXSqkMUzlVndS39rMGBQHfyLrdmOKMjgciWyWpjekXzRQuj1B7HqA==", "license": "MIT", "dependencies": { - "cli-table3": "^0.6.1", - "commander": "^9.1.0", - "date-fns": "^2.28.0", - "fn-args": "^5.0.0", - "fs-extra": "^10.0.1", - "lodash.filter": "^4.6.0", - "lodash.find": "^4.6.0", - "lodash.get": "^4.4.2", - "lodash.isempty": "^4.4.0", - "lodash.last": "^3.0.0", - "lodash.values": "^4.3.0", - "p-each-series": "^2.2.0" + "cli-table3": "^0.6.5", + "commander": "^14.0.2" }, "bin": { "migrate-mongo": "bin/migrate-mongo.js" }, "engines": { - "node": ">=8" + "node": ">=20.0.0" }, "peerDependencies": { "mongodb": "^4.4.1 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/migrate-mongo/node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/mime": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", @@ -10660,18 +10574,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-event": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", @@ -13105,6 +13007,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" diff --git a/package.json b/package.json index 45dbc1c2..7a957402 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "jwks-rsa": "^3.2.0", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", - "migrate-mongo": "^12.1.3", + "migrate-mongo": "^14.0.7", "mongoose": "^8.15.1", "morgan": "^1.10.1", "nanoid": "^5.1.5", From 8835f3f7758c62c49f96991e6c1dfc775eda583d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:09:46 -0500 Subject: [PATCH 158/370] build(npm): regenerate lockfile --- package-lock.json | 4592 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 4311 insertions(+), 281 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e81d430..b2f5cef6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,13 +125,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -454,6 +454,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", @@ -585,6 +588,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "version": "9.39.2", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", @@ -640,6 +646,16 @@ "node": ">=14" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -735,6 +751,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -805,6 +864,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -842,6 +917,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -877,6 +972,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.6.0.tgz", + "integrity": "sha512-bhmVJYSGqZnnhEWeQT5lP405+/qXQOZqeNalINP1qnckscz6Jo/JB6AJxwpz0OvJvHhWod8Vvm+3aUvHP2ID8Q==", "version": "4.6.0", "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.6.0.tgz", "integrity": "sha512-bhmVJYSGqZnnhEWeQT5lP405+/qXQOZqeNalINP1qnckscz6Jo/JB6AJxwpz0OvJvHhWod8Vvm+3aUvHP2ID8Q==", @@ -901,6 +999,9 @@ } }, "node_modules/@mongodb-js/saslprep": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz", + "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==", "version": "1.4.4", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz", "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==", @@ -1019,6 +1120,39 @@ "@octokit/openapi-types": "^26.0.0" } }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.1" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", + "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^26.0.0" + } + }, "node_modules/@octokit/plugin-retry": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", @@ -1716,9 +1850,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -2039,6 +2173,9 @@ } }, "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", @@ -2046,6 +2183,7 @@ "license": "MIT", "engines": { "node": ">=10" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -2204,9 +2342,9 @@ "license": "Apache-2.0" }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -2215,7 +2353,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -2739,37 +2877,29 @@ "node": ">=10" } }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" }, "engines": { - "node": ">=12" + "node": ">=8.0.0", + "npm": ">=5.0.0" } }, - "node_modules/cliui/node_modules/ansi-regex": { + "node_modules/cli-highlight/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", @@ -2779,7 +2909,7 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/ansi-styles": { + "node_modules/cli-highlight/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -2795,7 +2925,36 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cliui/node_modules/color-convert": { + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -2808,14 +2967,21 @@ "node": ">=7.0.0" } }, - "node_modules/cliui/node_modules/color-name": { + "node_modules/cli-highlight/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/cliui/node_modules/strip-ansi": { + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -2828,7 +2994,7 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/wrap-ansi": { + "node_modules/cli-highlight/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -2846,26 +3012,221 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", - "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^3.1.3", - "color-string": "^2.1.3" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=18" + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" } }, "node_modules/color-convert": { @@ -3123,6 +3484,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/conventional-changelog-writer": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", + "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-writer/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/conventional-commits-filter": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", @@ -3341,12 +3734,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, "engines": { "node": ">=12" }, @@ -3354,16 +3750,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=0.11" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" }, "funding": { "type": "opencollective", @@ -3570,6 +3992,49 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3909,6 +4374,9 @@ } }, "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "version": "9.39.2", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", @@ -3922,6 +4390,7 @@ "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3985,14 +4454,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4078,6 +4547,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4115,6 +4600,26 @@ "dev": true, "license": "MIT" }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4229,6 +4734,9 @@ } }, "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", @@ -4919,6 +5427,36 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -4970,6 +5508,65 @@ "node": ">= 0.6" } }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/from2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5776,6 +6373,20 @@ "node": ">=8" } }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -5834,6 +6445,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5851,6 +6478,10 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "node_modules/jest-diff/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5859,17 +6490,24 @@ "license": "MIT", "dependencies": { "color-name": "~1.1.4" + "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" + "node": ">=7.0.0" } }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "node_modules/jest-diff/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" + "license": "MIT" }, "node_modules/jest-get-type": { "version": "29.6.3", @@ -5913,6 +6551,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5930,6 +6584,10 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "node_modules/jest-matcher-utils/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5938,17 +6596,24 @@ "license": "MIT", "dependencies": { "color-name": "~1.1.4" + "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" + "node": ">=7.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "node_modules/jest-matcher-utils/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" + "license": "MIT" }, "node_modules/jest-message-util": { "version": "29.7.0", @@ -5987,6 +6652,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6004,6 +6685,10 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "node_modules/jest-message-util/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6012,17 +6697,24 @@ "license": "MIT", "dependencies": { "color-name": "~1.1.4" + "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" + "node": ">=7.0.0" } }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "node_modules/jest-message-util/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" + "license": "MIT" }, "node_modules/jest-util": { "version": "29.7.0", @@ -6058,6 +6750,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6095,6 +6803,26 @@ "dev": true, "license": "MIT" }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -6288,9 +7016,9 @@ } }, "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -6443,6 +7171,9 @@ "license": "MIT" }, "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", "version": "4.17.22", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", @@ -6614,6 +7345,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6651,6 +7398,26 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -7207,6 +7974,9 @@ } }, "node_modules/mongodb-memory-server": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.3.tgz", + "integrity": "sha512-CDZvFisXvGIigsIw5gqH6r9NI/zxGa/uRdutgUL/isuJh+inj0YXb7Ykw6oFMFzqgTJWb7x0I5DpzrqCstBWpg==", "version": "10.4.3", "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.3.tgz", "integrity": "sha512-CDZvFisXvGIigsIw5gqH6r9NI/zxGa/uRdutgUL/isuJh+inj0YXb7Ykw6oFMFzqgTJWb7x0I5DpzrqCstBWpg==", @@ -7214,6 +7984,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "mongodb-memory-server-core": "10.4.3", "mongodb-memory-server-core": "10.4.3", "tslib": "^2.8.1" }, @@ -7245,6 +8016,31 @@ "node": ">=16.20.1" } }, + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "node_modules/mongodb-memory-server-core": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.3.tgz", + "integrity": "sha512-IPjlw73IoSYopnqBibQKxmAXMbOEPf5uGAOsBcaUiNH/TOI7V19WO+K7n5KYtnQ9FqzLGLpvwCGuPOTBSg4s5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-mutex": "^0.5.0", + "camelcase": "^6.3.0", + "debug": "^4.4.3", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.11", + "https-proxy-agent": "^7.0.6", + "mongodb": "^6.9.0", + "new-find-package-json": "^2.0.0", + "semver": "^7.7.3", + "tar-stream": "^3.1.7", + "tslib": "^2.8.1", + "yauzl": "^3.2.0" + }, + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", @@ -7255,6 +8051,7 @@ "@types/webidl-conversions": "*" } }, + "node_modules/mongodb-memory-server-core/node_modules/bson": { "node_modules/mongodb-memory-server-core/node_modules/bson": { "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", @@ -7265,6 +8062,7 @@ "node": ">=16.20.1" } }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { "node_modules/mongodb-memory-server-core/node_modules/mongodb": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", @@ -7312,6 +8110,7 @@ } } }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", @@ -7324,6 +8123,9 @@ } }, "node_modules/mongoose": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.21.0.tgz", + "integrity": "sha512-dW2U01gN8EVQT5KAO5AkzjbqWc8A/CsEq15jOzq/M9ISpy8jw3iq7W9ZP135h9zykFOMt3AMxq4+anvt2YNJgw==", "version": "8.21.0", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.21.0.tgz", "integrity": "sha512-dW2U01gN8EVQT5KAO5AkzjbqWc8A/CsEq15jOzq/M9ISpy8jw3iq7W9ZP135h9zykFOMt3AMxq4+anvt2YNJgw==", @@ -7686,195 +8488,2916 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC" + "license": "ISC" + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.1", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.1", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.1", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.1", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.1", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.1", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.21", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.14", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" }, - "node_modules/normalize-url": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", - "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=14.16" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", - "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], + "inBundle": true, + "license": "MIT", "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.1", - "@npmcli/config": "^9.0.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.2.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.2.2", - "@npmcli/run-script": "^9.1.0", - "@sigstore/tuf": "^3.1.1", - "abbrev": "^3.0.1", - "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.4.1", - "ci-info": "^4.2.0", - "cli-columns": "^4.0.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^10.4.5", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.1.0", - "ini": "^5.0.0", - "init-package-json": "^7.0.2", - "is-cidr": "^5.1.1", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.1", - "libnpmexec": "^9.0.1", - "libnpmfund": "^6.0.1", - "libnpmhook": "^11.0.0", - "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.1", - "libnpmpublish": "^10.0.1", - "libnpmsearch": "^8.0.0", - "libnpmteam": "^7.0.0", - "libnpmversion": "^7.0.0", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^11.2.0", - "nopt": "^8.1.0", - "normalize-package-data": "^7.0.0", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.2", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^7.0.3", - "pacote": "^19.0.1", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "qrcode-terminal": "^0.12.0", - "read": "^4.1.0", - "semver": "^7.7.2", - "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^9.4.0", - "tar": "^6.2.1", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.1", - "which": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" + "ansi-regex": "^6.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "path-key": "^3.0.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", "dev": true, @@ -10581,6 +14104,19 @@ "node": ">= 0.8.0" } }, + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-event": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", @@ -10786,6 +14322,23 @@ "dev": true, "license": "MIT" }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, "node_modules/parse5-query-domtree": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse5-query-domtree/-/parse5-query-domtree-1.0.2.tgz", @@ -10872,15 +14425,39 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, "node_modules/path-scurry": { "version": "1.11.1", @@ -11141,9 +14718,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", + "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", "dev": true, "license": "MIT", "bin": { @@ -11157,6 +14734,9 @@ } }, "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "version": "1.0.1", "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", @@ -11184,6 +14764,21 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", @@ -11254,6 +14849,9 @@ } }, "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", @@ -11683,19 +15281,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/p-each-series": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", - "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semantic-release/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -11791,6 +15376,9 @@ } }, "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "version": "0.19.2", "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", @@ -11804,11 +15392,15 @@ "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -11852,6 +15444,9 @@ } }, "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "version": "1.16.3", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", @@ -11861,6 +15456,7 @@ "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -12297,6 +15893,50 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-combiner2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-combiner2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -12366,11 +16006,23 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12388,6 +16040,16 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/strip-ansi": { + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12404,8 +16066,32 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -12430,11 +16116,13 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12492,6 +16180,9 @@ } }, "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "version": "10.3.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", @@ -12502,10 +16193,12 @@ "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.5", + "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.14.1" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" @@ -12524,15 +16217,20 @@ } }, "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "version": "7.2.2", "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "cookie-signature": "^1.2.2", "methods": "^1.1.2", "superagent": "^10.3.0" + "superagent": "^10.3.0" }, "engines": { "node": ">=14.18.0" @@ -12548,6 +16246,16 @@ "node": ">=6.6.0" } }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12579,6 +16287,9 @@ } }, "node_modules/swagger-ui-dist": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", "version": "5.31.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", @@ -12603,9 +16314,9 @@ } }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12625,19 +16336,105 @@ "dev": true, "license": "MIT", "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=14.16" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tempy": { @@ -12829,6 +16626,50 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/time-span": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", @@ -12903,6 +16744,54 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -13183,6 +17072,16 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -13302,6 +17201,9 @@ } }, "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "version": "3.19.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", @@ -13388,6 +17290,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", @@ -13504,6 +17424,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13529,6 +17546,16 @@ "node": ">=10" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13621,6 +17648,9 @@ } }, "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "version": "4.3.5", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", From f19dfacfdfd95a2f773eec09e0ba63cb9b6ba706 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:10:24 -0500 Subject: [PATCH 159/370] refactor: move validate service to system folder --- app/services/{ => system}/validate-service.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/services/{ => system}/validate-service.js (100%) diff --git a/app/services/validate-service.js b/app/services/system/validate-service.js similarity index 100% rename from app/services/validate-service.js rename to app/services/system/validate-service.js From 71392d96cf7efb45218a3521c35a88018c7bf730 Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 16 Jan 2026 13:29:25 -0500 Subject: [PATCH 160/370] chore: update path for validation service --- app/controllers/validate-controller.js | 2 +- app/lib/validation-middleware.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js index 542f70e9..88832a5a 100644 --- a/app/controllers/validate-controller.js +++ b/app/controllers/validate-controller.js @@ -2,7 +2,7 @@ const { z } = require('zod'); const { StatusCodes } = require('http-status-codes'); -const validateService = require('../services/validate-service'); +const validateService = require('../services/system/validate-service'); const logger = require('../lib/logger'); const validationRequestSchema = z.object({ diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 56d912f9..306966ad 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -3,7 +3,7 @@ const { z, ZodError } = require('zod'); const { StatusCodes } = require('http-status-codes'); const logger = require('../lib/logger'); -const { processValidationIssues } = require('../services/validate-service'); +const { processValidationIssues } = require('../services/system/validate-service'); const { createAttackIdSchema, stixTypeToAttackIdMapping, From ce53fbf83ba21a0cecc3b683cc7d9b908bdbc7d9 Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 16 Jan 2026 13:36:20 -0500 Subject: [PATCH 161/370] chore: update package-lock to resume tests --- package-lock.json | 546 ++++++++++++++++++---------------------------- 1 file changed, 211 insertions(+), 335 deletions(-) diff --git a/package-lock.json b/package-lock.json index 037ff28f..191dad5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -899,13 +899,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -1228,9 +1228,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1359,9 +1359,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -1651,9 +1651,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.2.tgz", - "integrity": "sha512-4MzEkYcXqe5rWnlj6Iy/aE4+wXkAv3q7rJUOHairl24HtiTqDHxVavz96KJBNNpRyIcEXTlCm93AQpVp/uR4MA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.6.0.tgz", + "integrity": "sha512-bhmVJYSGqZnnhEWeQT5lP405+/qXQOZqeNalINP1qnckscz6Jo/JB6AJxwpz0OvJvHhWod8Vvm+3aUvHP2ID8Q==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", @@ -1675,9 +1675,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz", - "integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz", + "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -1932,9 +1932,9 @@ "license": "ISC" }, "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -2141,21 +2141,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@semantic-release/npm/node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/@semantic-release/npm/node_modules/get-stream": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", @@ -3150,9 +3135,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", - "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -3210,12 +3195,6 @@ "@types/node": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3232,9 +3211,9 @@ } }, "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "version": "25.0.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", + "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -3678,9 +3657,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", - "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -3689,7 +3668,7 @@ "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", - "qs": "^6.14.0", + "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" }, @@ -5419,9 +5398,9 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { @@ -5431,7 +5410,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5495,14 +5474,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", - "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.7" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5739,9 +5718,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6544,6 +6523,21 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7008,9 +7002,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -7864,12 +7858,11 @@ } }, "node_modules/jwks-rsa": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", - "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.1.tgz", + "integrity": "sha512-r7QdN9TdqI6aFDFZt+GpAqj5yRtMUv23rL2I01i7B8P2/g8F0ioEN6VeSObKgTLs4GmmNJwP9J7Fyp/AYDBGRg==", "license": "MIT", "dependencies": { - "@types/express": "^4.17.20", "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", "jose": "^4.15.4", @@ -7880,51 +7873,6 @@ "node": ">=14" } }, - "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/jwks-rsa/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/jwks-rsa/node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, "node_modules/jws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", @@ -8060,9 +8008,9 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", "dev": true, "license": "MIT" }, @@ -8798,42 +8746,15 @@ "whatwg-url": "^11.0.0" } }, - "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "license": "MIT", - "peer": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/mongodb-memory-server": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.1.tgz", - "integrity": "sha512-XpCyV1e7QQ1lW28rgtXP4ZlX8ZfD/8z1ZGNxz2y3JrosLgDrNnYWvPjlgFj3JjboYUtlh1jF2Ez/rwsQA6cl0w==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-10.4.3.tgz", + "integrity": "sha512-CDZvFisXvGIigsIw5gqH6r9NI/zxGa/uRdutgUL/isuJh+inj0YXb7Ykw6oFMFzqgTJWb7x0I5DpzrqCstBWpg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "mongodb-memory-server-core": "10.4.1", + "mongodb-memory-server-core": "10.4.3", "tslib": "^2.8.1" }, "engines": { @@ -8841,9 +8762,9 @@ } }, "node_modules/mongodb-memory-server-core": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.1.tgz", - "integrity": "sha512-YJdrEyF9hk64nfeoVDMP6IfTzK+gLZhrQqYyP6JJMsqo2LK5eF7JRZ4YPQDmt1re/JhItpiU+ypiZbIG1OsW5Q==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-10.4.3.tgz", + "integrity": "sha512-IPjlw73IoSYopnqBibQKxmAXMbOEPf5uGAOsBcaUiNH/TOI7V19WO+K7n5KYtnQ9FqzLGLpvwCGuPOTBSg4s5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8942,10 +8863,37 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/mongodb-memory-server-core/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/mongoose": { - "version": "8.20.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.2.tgz", - "integrity": "sha512-U0TPupnqBOAI3p9H9qdShX8/nJUBylliRcHFKuhbewEkM7Y0qc9BbrQR9h4q6+1easoZqej7cq2Ee36AZ0gMzQ==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.21.0.tgz", + "integrity": "sha512-dW2U01gN8EVQT5KAO5AkzjbqWc8A/CsEq15jOzq/M9ISpy8jw3iq7W9ZP135h9zykFOMt3AMxq4+anvt2YNJgw==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -9038,6 +8986,31 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/mongoose/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/morgan": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", @@ -9308,9 +9281,9 @@ "license": "ISC" }, "node_modules/normalize-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", - "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", "dev": true, "license": "MIT", "engines": { @@ -12200,6 +12173,19 @@ "node": ">= 0.8.0" } }, + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-event": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", @@ -12760,9 +12746,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", + "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", "dev": true, "license": "MIT", "bin": { @@ -12776,9 +12762,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -12873,9 +12859,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -13062,13 +13048,13 @@ } }, "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", "dev": true, "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^2.1.0" + "@pnpm/npm-conf": "^3.0.2" }, "engines": { "node": ">=14" @@ -13302,19 +13288,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/p-each-series": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", - "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semantic-release/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -13410,9 +13383,9 @@ } }, "node_modules/send": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", - "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -13421,13 +13394,13 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -13448,22 +13421,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -13476,15 +13433,6 @@ "node": ">=4" } }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -13496,105 +13444,20 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-static/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", @@ -14261,20 +14124,20 @@ } }, "node_modules/superagent": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", - "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "license": "MIT", "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.4", + "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.2" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" @@ -14293,19 +14156,30 @@ } }, "node_modules/supertest": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", - "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", "dev": true, "license": "MIT", "dependencies": { + "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^10.2.3" + "superagent": "^10.3.0" }, "engines": { "node": ">=14.18.0" } }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14337,9 +14211,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.30.3", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.3.tgz", - "integrity": "sha512-giQl7/ToPxCqnUAx2wpnSnDNGZtGzw1LyUw6ZitIpTmdrvpxKFY/94v1hihm0zYNpgp1/VY0jTDk//R0BBgnRQ==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -14361,9 +14235,9 @@ } }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14684,15 +14558,16 @@ } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "license": "MIT", + "peer": true, "dependencies": { - "punycode": "^2.3.1" + "punycode": "^2.1.1" }, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/traverse": { @@ -15031,16 +14906,17 @@ } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", "license": "MIT", + "peer": true, "dependencies": { - "tr46": "^5.1.0", + "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/which": { @@ -15060,9 +14936,9 @@ } }, "node_modules/winston": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", - "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -15379,9 +15255,9 @@ } }, "node_modules/zod": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" From ab79fb3d9c4baca209c8b170cd031c9e98458522 Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 16 Jan 2026 17:10:49 -0500 Subject: [PATCH 162/370] fix: remove unauthorized test errors --- app/tests/api/analytics/analytics-includeRefs.spec.js | 2 +- .../api/detection-strategies/detection-strategies-spec.js | 4 ++-- app/tests/api/relationships/relationships.spec.js | 4 ++-- app/tests/api/user-accounts/user-accounts.spec.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index 4ab470ad..3c41b146 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -377,7 +377,7 @@ describe('Analytics API - includeRefs Parameter', function () { .post('/api/analytics') .send(analyticWithBadRef) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(404); }); diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index 3b7eda8f..ac3ca9ae 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -168,7 +168,7 @@ describe('Detection Strategies API', function () { .post('/api/analytics') .send(initialPostContentForAnalytic1) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -176,7 +176,7 @@ describe('Detection Strategies API', function () { .post('/api/analytics') .send(initialPostContentForAnalytic2) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index a5661f52..80e2b6bc 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -376,7 +376,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -576,7 +576,7 @@ describe('Relationships API', function () { '/modified/' + relationship3a.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index 12c622c0..27e7c059 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -215,7 +215,7 @@ describe('User Accounts API', function () { .post('/api/user-accounts') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(409); }); From 8b151762b0d32b0b0678d8610c58d85fd1a6cd0f Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 16 Jan 2026 17:19:53 -0500 Subject: [PATCH 163/370] fix: remove unauthorized test errors --- app/api/definitions/paths/assets-paths.yml | 1 - app/tests/api/relationships/relationships.spec.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index 76aec764..8da69d7a 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -292,4 +292,3 @@ paths: description: 'The asset was successfully deleted.' '404': description: 'An asset with the requested STIX id and modified date was not found.' - diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 80e2b6bc..a5661f52 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -376,7 +376,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -576,7 +576,7 @@ describe('Relationships API', function () { '/modified/' + relationship3a.stix.modified, ) - .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) .expect(204); }); From 9672561523f8a71396cba6c225be95c2dfd8c2ca Mon Sep 17 00:00:00 2001 From: adpare Date: Sun, 18 Jan 2026 01:33:22 -0500 Subject: [PATCH 164/370] chore: update port to fix tests --- app/tests/integration-test/initialize-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/integration-test/initialize-data.js b/app/tests/integration-test/initialize-data.js index 623cbefe..e660a722 100644 --- a/app/tests/integration-test/initialize-data.js +++ b/app/tests/integration-test/initialize-data.js @@ -39,7 +39,7 @@ async function initializeData() { }; // Log into the Workbench REST API - const loginUrl = 'http://localhost:3000/api/authn/anonymous/login'; + const loginUrl = 'http://localhost:8080/api/authn/anonymous/login'; await login(loginUrl); // Import the collection index v1 into the database From 13390a3a465d66a1e2ae05a9a2220f1a756f255a Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 19 Jan 2026 13:24:06 -0500 Subject: [PATCH 165/370] fix: update zod version --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 191dad5c..3d78a457 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", "winston": "^3.17.0", - "zod": "^4.1.8" + "zod": "4.2.1" }, "devDependencies": { "@codedependant/semantic-release-docker": "^5.1.1", @@ -1651,9 +1651,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.6.0.tgz", - "integrity": "sha512-bhmVJYSGqZnnhEWeQT5lP405+/qXQOZqeNalINP1qnckscz6Jo/JB6AJxwpz0OvJvHhWod8Vvm+3aUvHP2ID8Q==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.3.tgz", + "integrity": "sha512-XYtV7dFosqZNMs04VG+X1xVktjI0blZKrpXfyKXMb8bum0dSucRegTGHBZDYqDQLNbFdzEfSh9IH92QT8SiI/Q==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", @@ -15255,9 +15255,9 @@ } }, "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 1b829de3..5f3d533d 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", "winston": "^3.17.0", - "zod": "^4.1.8" + "zod": "4.2.1" }, "devDependencies": { "@codedependant/semantic-release-docker": "^5.1.1", From 94e3401dfb94aaedd763e51d82b8fb81e5e90328 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 22 Jan 2026 12:20:31 -0500 Subject: [PATCH 166/370] chore: remove reports function from attack objects spec file --- .../api/attack-objects/attack-objects.spec.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index 724cf9b0..3724b6ba 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -271,23 +271,6 @@ describe('ATT&CK Objects API', function () { expect(attackObjects[0].stix.type).toBe('malware'); }); - it('GET /api/attack-objects/missing-linkbyid returns the object with an attack.mitre.org URL in the description', async function () { - const res = await request(app) - .get('/api/attack-objects/missing-linkbyid') - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - // We expect to get ATT&CK objects in an array - const attackObjects = res.body; - expect(attackObjects).toBeDefined(); - expect(Array.isArray(attackObjects)).toBe(true); - - expect(attackObjects.length).toBe(1); - expect(attackObjects[0].stix.name).toBe('software-2'); - }); - after(async function () { await database.closeConnection(); }); From a5c855dd51d682b0f60a53c70cbe185c866f5fd2 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 22 Jan 2026 12:21:38 -0500 Subject: [PATCH 167/370] fix: remove 401 validation errors and fix parallel relationships endpoint --- .../api/relationships/relationships.spec.js | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index a5661f52..8c0884a3 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -376,7 +376,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -399,7 +399,7 @@ describe('Relationships API', function () { .post('/api/relationships') .send(body) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201) .expect('Content-Type', /json/); @@ -408,28 +408,11 @@ describe('Relationships API', function () { expect(relationship3b).toBeDefined(); }); - it('GET /api/relationships/missing-linkbyid returns the relationship with an attack.mitre.org URL in the description', async function () { + it('GET /api/reports/parallel-relationships returns the parallel relationships', async function () { const res = await request(app) - .get('/api/relationships/missing-linkbyid') + .get('/api/reports/parallel-relationships') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect(200) - .expect('Content-Type', /json/); - - // We expect to get one relationship in an array - const mlRelationships = res.body; - expect(mlRelationships).toBeDefined(); - expect(Array.isArray(mlRelationships)).toBe(true); - - expect(mlRelationships.length).toBe(1); - expect(mlRelationships[0].stix.source_ref).toBe(sourceRef2); - }); - - it('GET /api/relationships/parallel returns the parallel relationships', async function () { - const res = await request(app) - .get('/api/relationships/parallel') - .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -437,10 +420,10 @@ describe('Relationships API', function () { // We expect to get a mapping of relationship key -> list of three parallel relationships const parallelRelationships = res.body; expect(parallelRelationships).toBeDefined(); - expect(Array.isArray(parallelRelationships)).toBe(true); - - expect(parallelRelationships.length).toBe(1); - expect(parallelRelationships[0].stix.source_ref).toBe(sourceRef2); + const pRelationships = Object.values(parallelRelationships)[0]; + expect(Array.isArray(pRelationships)).toBe(true); + expect(pRelationships.length).toBe(3); + expect(pRelationships[0].stix.source_ref).toBe(sourceRef2); }); it('GET /api/relationships returns the (latest) relationship matching a source_ref', async function () { @@ -576,7 +559,7 @@ describe('Relationships API', function () { '/modified/' + relationship3a.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); @@ -588,7 +571,7 @@ describe('Relationships API', function () { '/modified/' + relationship3b.stix.modified, ) - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(204); }); From 9e7842193e83c5e9f1459b9a6f18e81f684dfbca Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 22 Jan 2026 12:22:18 -0500 Subject: [PATCH 168/370] feat: add tests for reports endpoint --- app/tests/api/reports/reports.spec.js | 162 ++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 app/tests/api/reports/reports.spec.js diff --git a/app/tests/api/reports/reports.spec.js b/app/tests/api/reports/reports.spec.js new file mode 100644 index 00000000..0ea647bd --- /dev/null +++ b/app/tests/api/reports/reports.spec.js @@ -0,0 +1,162 @@ +const request = require('supertest'); +const { expect } = require('expect'); + +const database = require('../../../lib/database-in-memory'); +const databaseConfiguration = require('../../../lib/database-configuration'); +const AttackObject = require('../../../models/attack-object-model'); +const config = require('../../../config/config'); +const login = require('../../shared/login'); + +const logger = require('../../../lib/logger'); +logger.level = 'debug'; + +const targetRef2 = 'attack-pattern--d63a3fb8-9452-4e9d-a60a-54be68d5998c'; + +// test malware object +const malwareObject = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + id: 'malware--1c1ab115-f015-462c-92a0-f887277d8519', + name: 'software-2', + spec_version: '2.1', + type: 'malware', + description: + 'This is a malware type of software, with a URL that it should not have (https://attack.mitre.org/software/SW0001)', + is_family: false, + object_marking_refs: ['marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce'], + x_mitre_version: '1.1', + x_mitre_contributors: ['contributor-mk', 'contributor-cm'], + x_mitre_domains: ['mobile-attack'], + created: '2023-03-01T00:00:00.000Z', + modified: '2023-03-01T00:00:00.000Z', + }, +}; + +const initialObjectData = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + spec_version: '2.1', + type: 'relationship', + description: 'This is a relationship containing https://attack.mitre.org/.', + source_ref: malwareObject.stix.id, + relationship_type: 'uses', + target_ref: targetRef2, + external_references: [{ source_name: 'source-1', external_id: 's1' }], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--6444f546-6900-4456-b3b1-015c88d70dab', + }, +}; + +describe('Reports API', function () { + let app; + let passportCookie; + + before(async function () { + // Establish the database connection + // Use an in-memory database that we spin up for the test + await database.initializeConnection(); + + // Wait until the indexes are created + await AttackObject.init(); + + // Check for a valid database configuration + await databaseConfiguration.checkSystemConfiguration(); + + // Disable ADM validation for tests + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = true; + + // Initialize the express app + app = await require('../../../index').initializeApp(); + + // Log into the app + passportCookie = await login.loginAnonymous(app); + }); + + let software1; + it('POST /api/software creates a software', async function () { + // Further setup - need to index malware object with in database first + const body = malwareObject; + const res = await request(app) + .post('/api/software') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + software1 = res.body; + expect(software1).toBeDefined(); + expect(software1.stix).toBeDefined(); + expect(software1.stix.id).toBeDefined(); + expect(software1.stix.created).toBeDefined(); + expect(software1.stix.modified).toBeDefined(); + }); + + it('GET /api/reports/link-by-id/missing returns the object with an attack.mitre.org URL in the description', async function () { + const res = await request(app) + .get('/api/reports/link-by-id/missing') + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + // We expect to get ATT&CK objects in an array + const attackObjects = res.body; + expect(attackObjects).toBeDefined(); + expect(Array.isArray(attackObjects)).toBe(true); + + expect(attackObjects.length).toBe(1); + expect(attackObjects[0].stix.name).toBe('software-2'); + }); + + let relationship2; + it('POST /api/relationships creates a relationship', async function () { + const timestamp = new Date().toISOString(); + initialObjectData.stix.created = timestamp; + initialObjectData.stix.modified = timestamp; + initialObjectData.stix.source_ref = software1.stix.id; + initialObjectData.stix.target_ref = targetRef2; + const body = initialObjectData; + const res = await request(app) + .post('/api/relationships') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + // We expect to get the created relationship + relationship2 = res.body; + expect(relationship2).toBeDefined(); + }); + + it('GET /api/reports/link-by-id/missing returns the relationship with an attack.mitre.org URL in the description', async function () { + const res = await request(app) + .get('/api/reports/link-by-id/missing') + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + // We expect to get one relationship in an array + const mlRelationships = res.body; + expect(mlRelationships).toBeDefined(); + expect(Array.isArray(mlRelationships)).toBe(true); + + expect(mlRelationships.length).toBe(2); + expect(mlRelationships[0].stix.source_ref).toBe(software1.stix.id); + }); + + after(async function () { + await database.closeConnection(); + }); +}); From 4941046bafc1581333123f620f59a185702e8269 Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 22 Jan 2026 12:23:28 -0500 Subject: [PATCH 169/370] fix: 401 errors for middleware tests --- .../adm-validation-middleware.spec.js | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 1bc80909..8f1444ed 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -179,8 +179,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + expect(res.status).toBe(201); expect(res.body).toBeDefined(); @@ -211,8 +211,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + // .expect('Content-Type', /json/); // Should succeed because work-in-progress uses partial validation expect(res.status).toBe(201); @@ -238,8 +238,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + // Should fail validation expect(res.status).toBe(400); @@ -268,8 +268,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + expect(res.status).toBe(201); expect(res.body).toBeDefined(); @@ -296,8 +296,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + // Should fail validation because 'name' is required in full validation expect(res.status).toBe(400); @@ -325,8 +325,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + // Should fail validation expect(res.status).toBe(400); @@ -359,7 +359,7 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(createBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); createdObject = createRes.body; @@ -391,8 +391,8 @@ describe('ADM Validation Middleware', function () { .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + if (res.status !== 200) { logger.debug('=== REQUEST FAILED ==='); @@ -433,8 +433,8 @@ describe('ADM Validation Middleware', function () { .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + expect(res.status).toBe(200); }); @@ -460,8 +460,8 @@ describe('ADM Validation Middleware', function () { .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + expect(res.status).toBe(400); expect(res.body.error).toBeDefined(); @@ -490,7 +490,7 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(createBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(201); createdObject = createRes.body; @@ -519,8 +519,8 @@ describe('ADM Validation Middleware', function () { .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + expect(res.status).toBe(200); expect(res.body.stix.name).toBe('Reviewed Technique Name'); @@ -548,8 +548,7 @@ describe('ADM Validation Middleware', function () { .put(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) .send(updateBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); expect(res.status).toBe(400); expect(res.body.error).toBeDefined(); @@ -614,8 +613,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + // Should return 400 with validation error expect(res.status).toBe(400); @@ -645,8 +644,8 @@ describe('ADM Validation Middleware', function () { .post(endpoint) .send(requestBody) .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) - .expect('Content-Type', /json/); + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + expect(res.status).toBe(400); expect(res.body.error).toBe('Invalid data'); From 0068aa6d0d342acd733ae11c6499b1d41876015e Mon Sep 17 00:00:00 2001 From: adpare Date: Thu, 22 Jan 2026 12:23:59 -0500 Subject: [PATCH 170/370] chore: fix identation --- .../middleware/adm-validation-middleware.spec.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 8f1444ed..5effd4d5 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -180,7 +180,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - expect(res.status).toBe(201); expect(res.body).toBeDefined(); @@ -239,7 +238,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - // Should fail validation expect(res.status).toBe(400); @@ -269,7 +267,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - expect(res.status).toBe(201); expect(res.body).toBeDefined(); @@ -297,7 +294,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - // Should fail validation because 'name' is required in full validation expect(res.status).toBe(400); @@ -326,7 +322,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - // Should fail validation expect(res.status).toBe(400); @@ -392,7 +387,6 @@ describe('ADM Validation Middleware', function () { .send(updateBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - if (res.status !== 200) { logger.debug('=== REQUEST FAILED ==='); @@ -434,7 +428,6 @@ describe('ADM Validation Middleware', function () { .send(updateBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - expect(res.status).toBe(200); }); @@ -461,7 +454,6 @@ describe('ADM Validation Middleware', function () { .send(updateBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - expect(res.status).toBe(400); expect(res.body.error).toBeDefined(); @@ -520,7 +512,6 @@ describe('ADM Validation Middleware', function () { .send(updateBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - expect(res.status).toBe(200); expect(res.body.stix.name).toBe('Reviewed Technique Name'); @@ -614,7 +605,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - // Should return 400 with validation error expect(res.status).toBe(400); @@ -645,7 +635,6 @@ describe('ADM Validation Middleware', function () { .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - expect(res.status).toBe(400); expect(res.body.error).toBe('Invalid data'); From 0f9befa57bf13d9c398b85e40a274b825c43d909 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:45:31 -0500 Subject: [PATCH 171/370] chore: add .nocommit folder to .gitignore for local development artifacts --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index aa128085..41e16e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# A place to store artifacts during local development (scripts, datasets, dotenv files, etc.) +.nocommit/**/* + # Logs logs *.log From bc9fe72e9526e4ac7c6ca4aecd93efc9f33aabda Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:46:39 -0500 Subject: [PATCH 172/370] feat(openapi): add validateContents query paramater to POST /api/collection-bundles endpoint --- app/api/definitions/paths/collection-bundles-paths.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/api/definitions/paths/collection-bundles-paths.yml b/app/api/definitions/paths/collection-bundles-paths.yml index 05ca2f40..00c194d1 100644 --- a/app/api/definitions/paths/collection-bundles-paths.yml +++ b/app/api/definitions/paths/collection-bundles-paths.yml @@ -101,6 +101,14 @@ paths: - duplicate-collection - all example: 'attack-spec-version-violations' + - name: validateContents + in: query + description: | + If false, bypasses ATT&CK Data Model validation. Note that this may result in unexpected issues. + If true, validates the contents of the given STIX bundle using the ATT&CK Data Model. + schema: + type: boolean + default: true requestBody: required: true content: From 12a2485c75fdf24c883c67a343759e2c0d8e9ff5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:47:27 -0500 Subject: [PATCH 173/370] feat(exceptions): add new ValidationError and SchemaValidationError exception classes --- app/exceptions/index.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/exceptions/index.js b/app/exceptions/index.js index 98e5046d..9a190b98 100644 --- a/app/exceptions/index.js +++ b/app/exceptions/index.js @@ -184,6 +184,26 @@ class InvalidPostOperationError extends CustomError { } } +class ValidationError extends CustomError { + constructor(message = 'Validation failed', options) { + super(message, options); + } +} + +class SchemaValidationError extends CustomError { + constructor(schemaName, zodError, options = {}) { + const errorDetails = zodError.errors + .map((err) => `${err.path.join('.')}: ${err.message}`) + .join('; '); + + super(`Schema validation failed for ${schemaName}: ${errorDetails}`, { + ...options, + zodError, + schemaName, + }); + } +} + module.exports = { //** General errors */ NotImplementedError, @@ -197,6 +217,10 @@ module.exports = { ImmutablePropertyError, InvalidPostOperationError, + //** Validation errors */ + ValidationError, + SchemaValidationError, + //** Database-related errors */ DuplicateIdError, DuplicateEmailError, From 60e9b444634edba3347c2c1f639b6c284c38e829 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:50:04 -0500 Subject: [PATCH 174/370] feat(bundle-helpers): add validationError category to importErrors Responses to collection bundle import requests can now inform users of potential validation errors that occurred during importation. --- app/services/stix/collection-bundles-service/bundle-helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/stix/collection-bundles-service/bundle-helpers.js b/app/services/stix/collection-bundles-service/bundle-helpers.js index 25298209..adaaa353 100644 --- a/app/services/stix/collection-bundles-service/bundle-helpers.js +++ b/app/services/stix/collection-bundles-service/bundle-helpers.js @@ -23,6 +23,7 @@ module.exports.importErrors = { notInContents: 'Not in contents', // object in bundle but not in x_mitre_contents missingObject: 'Missing object', // object in x_mitre_contents but not in bundle saveError: 'Save error', + validationError: 'Validation error', attackSpecVersionViolation: 'ATT&CK Spec version violation', }; From 00fd84ba9521e2c391e6262157624dfcabcc4a36 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:43:34 -0500 Subject: [PATCH 175/370] docs(feature-planning): add draft spec for release track feature to replace collection bundles --- docs/COLLECTIONS_V2/00_API_REFERENCE.md | 953 +++++++++++++++ docs/COLLECTIONS_V2/01_SUMMARY.md | 233 ++++ docs/COLLECTIONS_V2/02_TERMINOLOGY.md | 399 +++++++ docs/COLLECTIONS_V2/03_VERSIONING.md | 180 +++ docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md | 1058 +++++++++++++++++ docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md | 871 ++++++++++++++ docs/COLLECTIONS_V2/06_ENTITIES.md | 395 ++++++ docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md | 138 +++ docs/COLLECTIONS_V2/99_ERROR_HANDLING.md | 53 + .../COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md | 121 ++ docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md | 107 ++ 11 files changed, 4508 insertions(+) create mode 100644 docs/COLLECTIONS_V2/00_API_REFERENCE.md create mode 100644 docs/COLLECTIONS_V2/01_SUMMARY.md create mode 100644 docs/COLLECTIONS_V2/02_TERMINOLOGY.md create mode 100644 docs/COLLECTIONS_V2/03_VERSIONING.md create mode 100644 docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md create mode 100644 docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md create mode 100644 docs/COLLECTIONS_V2/06_ENTITIES.md create mode 100644 docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md create mode 100644 docs/COLLECTIONS_V2/99_ERROR_HANDLING.md create mode 100644 docs/COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md create mode 100644 docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md diff --git a/docs/COLLECTIONS_V2/00_API_REFERENCE.md b/docs/COLLECTIONS_V2/00_API_REFERENCE.md new file mode 100644 index 00000000..6d460c11 --- /dev/null +++ b/docs/COLLECTIONS_V2/00_API_REFERENCE.md @@ -0,0 +1,953 @@ +# Release Tracks API V2 - API Reference + +## Overview + +This document provides the complete API reference for Release Tracks V2 (formerly "Collections V2"). + +**Related Documentation:** +- [01_SUMMARY.md](./01_SUMMARY.md) - High-level design summary and problem statement +- [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) - Complete terminology guide +- [03_VERSIONING.md](./03_VERSIONING.md) - Versioning and release process +- [04_VIRTUAL_TRACKS.md](./04_VIRTUAL_TRACKS.md) - Virtual release tracks (aggregations) +- [05_RELEASE_WORKFLOW.md](./05_RELEASE_WORKFLOW.md) - Workflow integration and candidacy +- [06_ENTITIES.md](./06_ENTITIES.md) - Database schemas and data models +- [07_OUTPUT_FORMATS.md](./07_OUTPUT_FORMATS.md) - Output format specifications + +**Quick Navigation:** +- [Ephemeral Release Tracks](#ephemeral-release-tracks) +- [Release Track Management](#release-track-management) +- [Snapshot-Specific Operations](#snapshot-specific-operations) +- [Candidate Management](#candidate-management) +- [Staged Objects](#staged-objects) +- [Configuration](#configuration) +- [Preview & Dry Run](#preview--dry-run) +- [Version Pin Management](#version-pin-management) +- [Virtual Release Tracks](#virtual-release-tracks) +- [Query Variations](#query-variations) +- [Output Formats](#output-formats) +- [Error Responses](#error-responses) + + +## Complete Endpoint List + +### Ephemeral Release Tracks +``` +GET /api/release-tracks/ephemeral/:domain +``` + +### Release Track Management +``` +GET /api/release-tracks +POST /api/release-tracks/new +POST /api/release-tracks/new-from-bundle +POST /api/release-tracks/import +GET /api/release-tracks/:id +POST /api/release-tracks/:id/meta +POST /api/release-tracks/:id/contents +POST /api/release-tracks/:id/bump +POST /api/release-tracks/:id/clone +DELETE /api/release-tracks/:id +``` + +### Snapshot Operations +``` +GET /api/release-tracks/:id/snapshots/:modified +POST /api/release-tracks/:id/snapshots/:modified/meta +POST /api/release-tracks/:id/snapshots/:modified/bump +POST /api/release-tracks/:id/snapshots/:modified/clone +DELETE /api/release-tracks/:id/snapshots/:modified +``` + +### Candidate Management +``` +POST /api/release-tracks/:id/candidates +GET /api/release-tracks/:id/candidates +DELETE /api/release-tracks/:id/candidates/:objectRef +POST /api/release-tracks/:id/candidates/review +POST /api/release-tracks/:id/candidates/promote +POST /api/release-tracks/:id/candidates/:objectRef/update-version +``` + +### Staged Objects +``` +GET /api/release-tracks/:id/staged +POST /api/release-tracks/:id/staged/demote +``` + +### Configuration +``` +GET /api/release-tracks/:id/config +PUT /api/release-tracks/:id/config +``` + +### Preview & Dry Run +``` +GET /api/release-tracks/:id/bump/preview +``` + +### Version Management +``` +GET /api/release-tracks/:id/objects/:objectRef/versions +``` + +### Virtual Release Tracks (Additional) +``` +PUT /api/release-tracks/:id/composition +POST /api/release-tracks/:id/snapshots/create +GET /api/release-tracks/:id/snapshots/preview +``` + +--- + +## Ephemeral Release Tracks + +"Ephemeral" release tracks refer to unmanaged, stateless release track snapshots. Upon request, a STIX bundle will be generated containing the latest copy of all objects contained within the respective domain as defined by the `:domain` path parameter. + +Three options are supported in the `:domain` path parameter: +- `enterprise` +- `ics` +- `mobile` + +These refer to all objects delineated by ATT&CK domain membership as reflected by the objects' `x_mitre_domains` property. + +### Get Ephemeral Bundle + +``` +GET /api/release-tracks/ephemeral/:domain +``` + +**Path Parameters:** +- `:domain` - `enterprise` | `ics` | `mobile` + +**Query Parameters:** +- `format` - `bundle` | `filesystemstore` | `workbench` (default: `bundle`) + +--- + +## Release Track Management + +### List All Release Tracks + +Retrieves a list of all release tracks (both standard and virtual) with summary information. + +``` +GET /api/release-tracks +``` + +**Query Parameters:** +- `releases` - `only` (filter to show only release tracks that have at least one tagged release) +- `type` - `standard` | `virtual` (filter by track type) +- `limit` - Number of results (pagination) +- `offset` - Pagination offset + +**Response Example:** +```json +{ + "release_tracks": [ + { + "id": "release-track--123", + "type": "standard", + "name": "Enterprise ATT&CK", + "description": "Enterprise domain release track", + "latest_version": "14.1", + "latest_modified": "2024-01-15T16:20:00Z", + "snapshot_count": 47, + "tagged_release_count": 12 + }, + { + "id": "release-track--456", + "type": "virtual", + "name": "Aggregated Enterprise", + "description": "Virtual aggregation of multiple tracks", + "latest_version": null, + "latest_modified": "2024-01-10T10:00:00Z", + "snapshot_count": 3, + "tagged_release_count": 2 + } + ], + "total": 2, + "limit": 10, + "offset": 0 +} +``` + +### Create New Release Track + +``` +POST /api/release-tracks/new +``` + +**Request Body:** +```json +{ + "name": "Release Track Name", + "description": "Description", + "external_references": [], + "object_marking_refs": [] +} +``` + +### Bootstrap Release Track From Bundle + +Creates a new release track initialized with objects from a STIX bundle. This is useful for importing existing collections or bootstrapping from published ATT&CK releases. + +``` +POST /api/release-tracks/new-from-bundle +``` + +**Request Body:** +```json +{ + "type": "bundle", + "id": "bundle--9ed7099a-63b8-4e49-92c7-547d39aa29e0", + "objects": [ + { + "type": "attack-pattern", + "id": "attack-pattern--uuid1", + "name": "Technique A" + }, + { + "type": "malware", + "id": "malware--uuid2", + "name": "Malware B" + } + ] +} +``` + +**Response:** +```json +{ + "release_track_id": "release-track--new-uuid", + "snapshot_id": "2024-01-15T10:00:00.000Z", + "objects_imported": 2, + "initial_tier": "members" +} +``` + +**Note:** All objects are added directly to the `members` tier. To add objects as candidates instead, use the standard [Create New Release Track](#create-new-release-track) endpoint followed by [Add Candidates](#add-candidates). + +### Import Release Track (Not Implemented) + +Comprehensively importing a release track would necessitate including the full snapshot history of the source release track. We don't presently have a solution for serializing an entire release track, including its snapshot history, into an atomic structure that can be exchanged between different Workbench deployments. + +However, we can viably "bootstrap" a new release track from a given STIX bundle (see [Bootstrap Release Track From Bundle](#bootstrap-release-track-from-bundle)). + +This endpoint should return/throw a `NotImplementedError` exception with HTTP status 501 (Not Implemented) until such a solution has been designed. + +``` +POST /api/release-tracks/import +``` + +**Request Body:** TBD + +**Status:** Not Implemented (501) + +### Get Latest Snapshot + +Retrieves the most recent snapshot from the release track (by `modified` timestamp). + +``` +GET /api/release-tracks/:id +``` + +**Query Parameters:** + +| Parameter | Values | Description | +|-----------|--------|-------------| +| `format` | `bundle` \| `filesystemstore` \| `workbench` | Output format (default: `bundle`) | +| `include` | `staged` \| `candidates` \| `all` | Which tiers to include (default: members only) | +| `releases` | `only` | Return only the latest tagged release instead of latest snapshot | +| `version` | `X.Y` | Return specific version (e.g., `14.1`) | +| `versions` | `all` | List all snapshots with metadata | + +**Examples:** + +```bash +# Get latest snapshot as STIX bundle (members only) +GET /api/release-tracks/:id + +# Get latest snapshot with all tiers in workbench format +GET /api/release-tracks/:id?include=all&format=workbench + +# Get latest tagged release (not draft) +GET /api/release-tracks/:id?releases=only + +# Get specific version +GET /api/release-tracks/:id?version=14.1 + +# List all snapshots +GET /api/release-tracks/:id?versions=all +``` + +### Update Metadata + +A user or team may wish to: +- rename a release (e.g., fix a typo like `"Entrprise"` to `"Enterprise"`) or shift the scope/purpose of an existing release track without losing its history (though [cloning](#clone-latest-snapshot) is preferred in this scenario) +- update metadata (which at present consists of a `description` field, `object_marking_references` (typically only includes the global marking definition) and the author (`created_by_ref`). +``` +POST /api/release-tracks/:id/meta +``` + +Creates new snapshot with updated metadata. + +**Request Body:** +```json +{ + "name": "Updated Name", + "description": "Updated description", + "external_references": [], + "object_marking_refs": [] +} +``` + +### Update Contents + +``` +POST /api/release-tracks/:id/contents +``` + +Creates new snapshot with updated member objects. **This is intended for retroactive hotfixes only.** The main workflow for enrolling new member objects into `x_mitre_contents` is through the candidate-staging promotion cycle described in [03_VERSIONING.md](./03_VERSIONING.md). + +**Request Body:** +```json +{ + "x_mitre_contents": ["attack-pattern--uuid1", "malware--uuid2"] +} +``` + +### Bump/Tag Latest Snapshot + +Converts the latest draft snapshot to a tagged release. Tags the snapshot in-place (does not create new snapshot). Dynamically sets `x_mitre_version` based on the request body options. + +- If `version` is provided, uses that exact version (must be `X.Y` format) +- If `type` is provided, calculates next version based on bump type +- If omitted, defaults to minor bump +- If this is the first release, the version will be `1.0` + +``` +POST /api/release-tracks/:id/bump +``` + +**Request Body (optional):** +```json +{ + "type": "major" | "minor", // Defaults to "minor" if omitted + "version": "X.Y", // Alternative: explicit version + "dry_run": true // Optional: preview without persisting +} +``` + +### Clone Release Track From Latest + +Bootstraps a new `release-track` instance from an existing snapshot. +``` +POST /api/release-tracks/:id/clone +``` + +**Request Body:** +```json +{ + "name": "Cloned Release Track" // optional +} +``` + +### Delete Release Track + +``` +DELETE /api/release-tracks/:id +``` + +**Query Parameters:** +- `versions` - `latest` (delete only latest, default: all) + +--- + +## Snapshot-Specific Operations + +All operations in this section operate on a specific snapshot identified by its `modified` timestamp. + +### Get Specific Snapshot + +Retrieves a specific snapshot by its `modified` timestamp. + +``` +GET /api/release-tracks/:id/snapshots/:modified +``` + +**Path Parameters:** +- `:modified` - ISO 8601 timestamp (e.g., `2024-01-15T16:20:00.000Z`) + +**Query Parameters:** +- `format` - `bundle` | `filesystemstore` | `workbench` (default: `bundle`) +- `include` - `staged` | `candidates` | `all` (default: members only) + +**Example:** +```bash +# Get snapshot from January 15, 2024 as STIX bundle +GET /api/release-tracks/:id/snapshots/2024-01-15T16:20:00.000Z + +# Get with all tiers in workbench format +GET /api/release-tracks/:id/snapshots/2024-01-15T16:20:00.000Z?include=all&format=workbench +``` + +### Update Metadata (Specific Snapshot) + +``` +POST /api/release-tracks/:id/snapshots/:modified/meta +``` + +Creates new snapshot with updated metadata. + +**Request Body:** Same as [Update Metadata](#update-metadata) for latest snapshot. + +### Update Contents (Specific Snapshot) + +``` +POST /api/release-tracks/:id/snapshots/:modified/contents +``` + +Creates new snapshot with updated member objects. **This is intended for retroactive hotfixes only.** + +**Request Body:** Same as [Update Contents](#update-contents) for latest snapshot. + +### Bump/Tag Specific Snapshot + +Converts a specific draft snapshot to a tagged release. Tags snapshot in-place (does not create new snapshot). + +``` +POST /api/release-tracks/:id/snapshots/:modified/bump +``` + +**Request Body:** Same as [Bump/Tag Latest Snapshot](#bumptag-latest-snapshot). + +### Clone Specific Snapshot + +Bootstraps a new release track from the specified snapshot. +``` +POST /api/release-tracks/:id/snapshots/:modified/clone +``` + +### Delete Specific Snapshot + +**TODO**: further consideration needs to be given here. We need to be careful to avoid breaking contextual continuity between snapshots. +``` +DELETE /api/release-tracks/:id/snapshots/:modified +``` + +--- + +## Candidate Management + +### Add Candidates + +Adds STIX objects as candidates to the latest draft snapshot. Each object is identified by its `stix.id` field, as well as (optionally) its `stix.modified` field. If `stix.modified` is omitted, the latest permutation of the relevant STIX object will be added. The candidacy reference will follow the latest version of the object until the moment the draft is converted to a release, at which point the reference will become locked to the specific permutation of the object that was considered "latest" at the time the release bump occurred. + +``` +POST /api/release-tracks/:id/candidates +``` + +**Request Body:** +```json +{ + "object_refs": [ + {"id": "attack-pattern--uuid", "modified": "2024-01-15T10:00:00Z"}, // pinned to specific version + {"id": "malware--uuid"} // follows latest version while marked as candidate + ] +} +``` + +Simplified (uses latest versions): +```json +{ + "object_refs": ["attack-pattern--uuid", "malware--uuid"] +} +``` + +### List Candidates + +Retrieves the list of candidate objects from the latest snapshot. + +``` +GET /api/release-tracks/:id/candidates +``` + +**Query Parameters:** +- `status` - Filter by workflow status: `work-in-progress` | `awaiting-review` | `reviewed` + +**Response Example:** +```json +{ + "candidates": [ + { + "object_ref": "attack-pattern--eee", + "object_modified": "2024-01-12T09:00:00Z", + "object_name": "New Technique XYZ", + "object_type": "attack-pattern", + "status": "work-in-progress", + "added_at": "2024-01-10T10:00:00Z", + "added_by": "alice@example.com" + }, + { + "object_ref": "malware--fff", + "object_modified": "2024-01-13T14:00:00Z", + "object_name": "New Malware ABC", + "object_type": "malware", + "status": "awaiting-review", + "added_at": "2024-01-12T14:30:00Z", + "added_by": "bob@example.com" + } + ], + "total": 2 +} +``` + +### Remove Candidate + +Remove an object from the latest snapshot's candidates list (`workspace.candidates`). +``` +DELETE /api/release-tracks/:id/candidates/:objectRef +``` + +### Bulk Object Status Transition + +Bulk transition candidate objects currently in the latest snapshot from workflow status `from` to workflow status `to`. +- Optionally target specific candidates using the `object_refs` filter. +- `object_refs` is optional; if omitted, transitions all matching `from` status. + +Bidirectional status transition is supported here. For example, objects can be transition from "reviewed" → "awaiting-review" or from "awaiting-review" → "work-in-progress". + +Notably, changes to an object's status (e.g., "work-in-progress" → "awaiting-review") will automatically update its release track membership standing (e.g., candidate, staged, member). In the most restrictive (typical) scenario, a candidate object transitioning to the "reviewed" state will trigger a new draft snapshot creation wherein the object is now staged. +``` +POST /api/release-tracks/:id/candidates/review +``` + +**Request Body:** +```json +{ + "from": "work-in-progress", + "to": "awaiting-review", + "object_refs": [ + {"id": "attack-pattern--uuid", "modified": "2024-01-15T10:00:00Z"} + ] +} +``` + +--- + +## Staged Objects + +### List Staged Objects + +Retrieves the list of staged objects from the latest snapshot. Staged objects are ready for the next tagged release. + +``` +GET /api/release-tracks/:id/staged +``` + +**Response Example:** +```json +{ + "staged": [ + { + "object_ref": "attack-pattern--ddd", + "object_modified": "2024-01-14T10:00:00Z", + "object_name": "Reviewed Technique", + "object_type": "attack-pattern", + "status": "reviewed", + "staged_at": "2024-01-14T11:00:00Z", + "staged_by": "reviewer@example.com" + } + ], + "total": 1 +} +``` + +### Promote Candidate Objects To Staged + +``` +POST /api/release-tracks/:id/candidates/promote +``` + +**Request Body:** +```json +{ + "object_refs": ["attack-pattern--eee"] +} +``` + +**Response:** +```json +{ + "promoted": [ + { + "object_ref": "attack-pattern--eee", + "status": "work-in-progress", + "warning": "Object is not reviewed, manual override applied" + } + ] +} +``` + +### Demote Staged Objects To Candidates + +``` +POST /api/release-tracks/:id/staged/demote +``` + +**Request Body:** +```json +{ + "object_refs": [ + {"id": "attack-pattern--uuid", "modified": "2024-01-15T10:00:00Z"} + ] +} +``` + +--- + +## Configuration + +### Get Configuration + +``` +GET /api/release-tracks/:id/config +``` + +### Update Configuration + +``` +PUT /api/release-tracks/:id/config +``` + +**Request Body:** +```json +{ + "candidacy_threshold": "work-in-progress" | "awaiting-review" | "reviewed", + "auto_promote": true | false, + "include_candidates_in_snapshots": true | false +} +``` + +--- + +## Preview & Dry Run + +### Preview Next Release (Read-Only) + +Shows a verbose diff of what will change in the next tagged release without creating any data. + +``` +GET /api/release-tracks/:id/bump/preview +``` + +**Query Parameters:** +- `format` - `bundle` | `filesystemstore` | `workbench` (default: `workbench`) + +**Response Example:** +```json +{ + "current_version": "1.1", + "next_version": "1.2", + "release_preview": { + "will_include": [ + { + "ref": "attack-pattern--ddd", + "name": "New Technique XYZ", + "status": "reviewed", + "source": "staged" + } + ], + "will_exclude": [ + { + "ref": "attack-pattern--eee", + "name": "WIP Technique", + "status": "work-in-progress", + "reason": "Does not meet candidacy threshold" + } + ] + } +} +``` + +### Dry Run Bump (Returns Exact Output) + +Performs all bump logic and returns the exact release contents without persisting changes to the database. + +``` +POST /api/release-tracks/:id/bump +``` + +**Request Body:** +```json +{ + "type": "minor", + "dry_run": true +} +``` + +**Response:** Returns the exact snapshot that would be created, with all objects and metadata. + +--- + +## Version Pin Management + +### Update Candidate Version Pin + +Updates which version of an object a candidate reference is pinned to. This allows upgrading a candidate to track a newer version of an object, or downgrading to a previous version. + +``` +POST /api/release-tracks/:id/candidates/:objectRef/update-version +``` + +**Request Body:** +```json +{ + "old_modified": "2024-01-15T10:00:00Z", + "new_modified": "2024-01-20T14:00:00Z" +} +``` + +**Use Cases:** +- Upgrading a candidate to the latest version of an object +- Downgrading to a previous stable version +- Synchronizing with another release track's version + +**Note:** This operation creates a new draft snapshot with the updated version pin. + +### List Object Versions in Release Track + +Lists all versions of a specific object referenced across all tiers (candidates, staged, members) in the release track. + +``` +GET /api/release-tracks/:id/objects/:objectRef/versions +``` + +**Response Example:** +```json +{ + "object_ref": "attack-pattern--T1234", + "versions": [ + { + "modified": "2024-02-15T10:00:00Z", + "tier": "candidates", + "status": "work-in-progress" + }, + { + "modified": "2024-02-01T14:00:00Z", + "tier": "members", + "status": "reviewed" + } + ] +} +``` + +--- + +## Output Formats + +### `bundle` (Default) + +Standard STIX 2.1 bundle. + +### `filesystemstore` + +STIX FileSystemStore directory structure. + +### `workbench` + +Custom format with workflow metadata for UI. + +--- + +## Error Responses + +### AlreadyReleasedError + +**Status:** 409 Conflict + +Snapshot already has a version assigned. + +### InvalidVersionError + +**Status:** 400 Bad Request + +Invalid version format or not greater than previous versions. + +### NotFoundError + +**Status:** 404 Not Found + +Release track not found. + +--- + +## Virtual Release Tracks + +Virtual release tracks are computed aggregations of other release tracks. Unlike standard tracks, virtual tracks don't directly manage objects through the candidate → staged → released workflow. Instead, they compose content from multiple "component tracks" based on configurable rules. + +**Key Characteristics:** +- Compute contents from component standard or virtual tracks +- Only reference **tagged snapshots** from component tracks (never drafts) +- Create snapshots **manually or on schedule** (never event-driven) +- All snapshots start as **drafts** and must be explicitly tagged +- Support **resolution strategies** to control which component versions are included + +**Resolution Strategies:** +1. `latest_tagged` - Always use the most recent tagged snapshot from component +2. `specific_version` - Pin to a specific semantic version (e.g., "5.0") +3. `specific_snapshot` - Pin to a specific snapshot by timestamp + +See [04_VIRTUAL_TRACKS.md](./04_VIRTUAL_TRACKS.md) for complete documentation. + +### Create Virtual Track + +``` +POST /api/release-tracks/new +``` + +**Request Body:** +```json +{ + "type": "virtual", + "name": "Enterprise ATT&CK", + "description": "Virtual aggregation of Enterprise content", + "composition": { + "component_tracks": [ + { + "track_id": "GroupsMonthly--uuid", + "resolution_strategy": "latest_tagged", + "filters": { + "object_types": ["intrusion-set"] + } + } + ], + "deduplication": { + "strategy": "prefer_latest_modified", + "tier_resolution": "highest_tier", + "status_resolution": "highest_status" + } + }, + "snapshot_schedule": { + "mode": "cron", + "cron": "0 0 1 1,7 *" + } +} +``` + +### Update Virtual Track Composition + +``` +PUT /api/release-tracks/:id/composition +``` + +**Request Body:** +```json +{ + "component_tracks": [ + { + "track_id": "GroupsMonthly--uuid", + "resolution_strategy": "latest_tagged" + }, + { + "track_id": "TechniquesQuarterly--uuid", + "resolution_strategy": "specific_version", + "version": "2.0" + } + ] +} +``` + +**Note:** Updating composition creates a new draft snapshot with the new composition rules. + +### Create Virtual Snapshot + +``` +POST /api/release-tracks/:id/snapshots/create +``` + +**Request Body:** +```json +{ + "description": "Q1 2024 snapshot" +} +``` + +**Response:** +```json +{ + "stix": { + "id": "x-mitre-collection--virtual-uuid", + "modified": "2024-03-01T10:00:00Z", + "x_mitre_version": null, + "type": "virtual" + }, + "composition_resolution": { + "resolved_at": "2024-03-01T10:00:00Z", + "component_snapshots": [ + { + "track_id": "GroupsMonthly--uuid", + "track_name": "Groups Monthly", + "resolved_snapshot": "2024-02-15T10:00:00Z", + "resolved_version": "5.2", + "strategy_used": "latest_tagged", + "object_count": 47 + } + ], + "total_objects": 870, + "duplicates_resolved": 0 + } +} +``` + +### Preview Virtual Snapshot + +Preview what a snapshot would contain without creating it: + +``` +GET /api/release-tracks/:id/snapshots/preview +``` + +**Response:** +```json +{ + "preview": { + "would_resolve_to": { + "component_snapshots": [...], + "total_objects": 870 + }, + "comparison_to_latest_tagged": { + "current_version": "13.1", + "new_objects": 12, + "updated_objects": 45, + "removed_objects": 3 + } + } +} +``` + +--- + +## Query Variations + +All `GET` endpoints for release tracks and snapshots support these query parameters: + +**Include Parameter** (controls which tiers are returned): +``` +GET /api/release-tracks/:id # Default: members only +GET /api/release-tracks/:id?include=staged # Include staged tier +GET /api/release-tracks/:id?include=candidates # Include candidates tier +GET /api/release-tracks/:id?include=all # Include all tiers +``` + +**Format Parameter** (controls output format): +``` +GET /api/release-tracks/:id?format=bundle # Standard STIX 2.1 bundle (default) +GET /api/release-tracks/:id?format=filesystemstore # STIX FileSystemStore structure +GET /api/release-tracks/:id?format=workbench # Workbench format with metadata +``` + +**Combined Example:** +``` +GET /api/release-tracks/:id?include=all&format=workbench +``` \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/01_SUMMARY.md b/docs/COLLECTIONS_V2/01_SUMMARY.md new file mode 100644 index 00000000..23c893f3 --- /dev/null +++ b/docs/COLLECTIONS_V2/01_SUMMARY.md @@ -0,0 +1,233 @@ +# Release Tracks API V2 - Design Summary + +## Overview + +This document provides a high-level summary of the Release Tracks API refactor (a.k.a. "Collections V2"), tying together the versioning system and workflow integration. + +**Note on Terminology:** We're replacing the overloaded term "collection" with **release track** to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) for complete terminology guide. + +## Problem Statement + +The existing Collections API has five major issues: + +1. **Confusing API design** - Three separate routers (stix-bundles, collection-bundles, collections) for related functionality +2. **No version control** - Release tracks can't be tagged as releases or track version history +3. **No workflow integration** - Thousands of objects being developed in parallel with no way to filter by readiness state +4. **STIX freeze bottleneck** - Objects frozen for one release can't be modified for the next release, blocking parallel development +5. **Cross-track duplication** - Teams want to track the same objects across release tracks with different cadences (e.g., monthly Groups releases + twice-yearly Enterprise releases), leading to duplicate tracking overhead and concept fatigue + +## Solution Architecture + +### 0. Standard and Virtual Release Tracks + +The Release Tracks API supports two types of release tracks: + +**Standard Release Tracks** - Direct object lifecycle management (the traditional model) +- Manage objects through the candidate → staged → released workflow +- Source of truth for specific object types or content domains +- Create snapshots when objects are added/removed or configuration changes +- Examples: "GroupsMonthly", "TechniquesQuarterly", "SoftwareBiannual" + +**Virtual Release Tracks** - Computed aggregations of other release tracks (NEW) +- Compose content from multiple standard (or other virtual) tracks +- No duplicate object tracking - objects managed in source tracks only +- Create snapshots manually or on schedule (never event-driven) +- Always compose from tagged snapshots only (never drafts) +- Examples: "EnterpriseTwiceAnnual" (aggregates Groups + Techniques + Software) + +**Use Case for Virtual Tracks:** + +Teams can organize objects into modular standard tracks by type (e.g., one track for Groups, one for Techniques), each with its own release cadence. Then create virtual tracks that aggregate these into domain-specific releases (e.g., Enterprise ATT&CK = Groups + Techniques + Software) without duplicating object tracking. + +See [04_VIRTUAL_TRACKS.md](./04_VIRTUAL_TRACKS.md) for complete virtual track documentation. + +### 1. Unified API Structure + +**Old API:** +``` +GET /api/stix-bundles (ephemeral bundles) +GET /api/collection-bundles (export) +POST /api/collection-bundles (import) +GET /api/collections (list) +POST /api/collections (create) +GET /api/collections/:id (retrieve) +``` + +**New API V2 (partial preview):** + +The new API is still a work in progress. The source of truth is located in [00_API_REFERENCE.md](./00_API_REFERENCE.md). The following is a preview. If there are any discrepencies between what is shown here and what is shown in [00_API_REFERENCE.md](./00_API_REFERENCE.md), defer to the latter. +``` +# Ephemeral bundles (stateless) +GET /api/release-tracks/ephemeral/:domain + +# Release track management +POST /api/release-tracks/new +GET /api/release-tracks/:id +POST /api/release-tracks/:id/config +POST /api/release-tracks/:id/meta +POST /api/release-tracks/:id/clone +PUT /api/release-tracks/:id/bump +POST /api/release-tracks/:id/archive +DELETE /api/release-tracks/:id + +# Candidate/workflow management +POST /api/release-tracks/:id/candidates +POST /api/release-tracks/:id/candidates/review + +# Snapshot-specific operations +GET /api/release-tracks/:id/snapshots/:modified +POST /api/release-tracks/:id/snapshots/:modified/config +POST /api/release-tracks/:id/snapshots/:modified/meta +POST /api/release-tracks/:id/snapshots/:modified/clone +DELETE /api/release-tracks/:id/snapshots/:modified +PUT /api/release-tracks/:id/snapshots/:modified/bump +``` + +### 2. Git-Inspired Versioning + +We borrow heavily concepts from git. Snapshots are sort of like commits and tagged releases are like git tags. A release track contains snapshots: delta permutations that can be linearly tracked to deduce how the release track has evolved over time. A snapshot is generated every time a change is made, whether that be adding/removing objects, updating the release track configuration, or renaming the release track altogether. + +**Snapshots** (like Git commits) +- Every modification creates a new snapshot +- Identified by `stix.modified` timestamp +- Immutable once created +- Complete audit trail +- May be a **draft release** (untagged) or **tagged release** (has version number) + +**Tagged Releases** (like Git tags) +- Snapshots are tagged with `x_mitre_version`. Draft snapshots are denoted by the fact that their `x_mitre_version` key is set to `null`. +- Uses MAJOR.MINOR versioning (not MAJOR.MINOR.PATCH), as specified by `x_mitre_version` +- Snapshots are tagged in-place (no duplicate data) +- When a snapshot is tagged/released, an event is captured in its `workspace.version_history` array +- Once a snapshot is tagged, it cannot be re-tagged. Tagged snapshots are **immutable**. + +### 3. Three-Tier Workflow Integration with Version Pinning + +We use the preexisting object workflow statuses, `work-in-progress`, `awaiting-review`, and `reviewed`, to control each object's "standing" in a release track. + +There are three types of "standings": + 1. **Candidate**: When an object is first added to a release track, is it considered a candidate. It does not have full membership yet; if the snapshot were to be tagged and released right now, candidates would not be included. + 2. **Staged**: Once a candidate's workflow status meets the release track's ["candidacy threshold"](./4_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `x_mitre_contents`. + 3. **Member**: Objects are considered "members" if they are "cooked" into the `x_mitre_contents` array of the current snapshot. These are considered already released. + +Thus, we now have a viable solution for the classic "STIX freeze" dilemma wherein editors cannot begin working on the next-next release until all objects in the next release have been released. Staged objects are locked in for the imminent release, but editors are free to continue iterating on future object changes and can queue them up as candidates without affecting the permutation that has already been staged for the imminent release. + +Candidates and staged objects alike can be be statically pinned to specific versions via `stix.id` and `stix.modified` couplings, or maintain dynamic/moving references to object versions by omitting `stix.modified`. In the latter, scenario, the release track will effectively "follow" the latest permutation of the relevant object until the moment a release snapshot is generated, at which point the latest permutation will become "locked in" to `x_mitre_contents` via the `stix.id` and `stix.modified` keys of the latest permutation of the object that existed at the time of the release. + +## Key Features + +### Automatic Promotion + +Object versions automatically move between tiers based on release track-scoped workflow status: + +``` +Object version added to release track + → track-scoped status = "work-in-progress" + → Added to workspace.candidates with version pin + +Object status changed in release track + → track-scoped status = "reviewed" + → Auto-promoted to workspace.staged (version pin preserved) + +Snapshot tagged + → workspace.staged entries → stix.x_mitre_contents (version pins preserved) +``` + +### Configurable Thresholds + +Each release track can set its own candidacy threshold: + +```javascript +workspace.config.candidacy_threshold = "reviewed" // Default +workspace.config.candidacy_threshold = "awaiting-review" // Permissive +workspace.config.candidacy_threshold = "work-in-progress" // Very permissive +``` + +### Multiple Output Formats + +- **bundle** - Standard STIX 2.1 bundle (for publication) +- **filesystemstore** - STIX FileSystemStore directory structure +- **workbench** - Custom format with workflow metadata (for UI) + +### Dry Run + Preview + +"Preview" will provide a verbose/detailed diff of what will change in the next release +``` +GET /api/release-tracks/:id/bump/preview + ?format = bundle | filesystemstore | workbench +``` + +"Dry-run" will output the literal/exact contents of the would-be tagged release +``` +POST /api/release-tracks/:id/bump +{ + "type": "major", + "dry_run": true <-- IMPORTANT!! +} +``` + +Shows exactly what will be in the next release before bumping. + +### Bulk Operations + +``` +POST /api/release-tracks/:id/candidates/review +{ + "from": "awaiting-review", + "to": "reviewed" +} +``` + +Transition all candidates matching status in one operation. + +## State Diagram + +``` +┌─────────────────────────────────────────────────────────┐ +│ RELEASE TRACK LIFECYCLE │ +└─────────────────────────────────────────────────────────┘ + +Release Track Created +(x_mitre_version: null) + │ + ↓ +Add/Update Objects ────────────────┐ + │ │ + ↓ │ +New Snapshot Created │ +(stix.modified updated) │ +(x_mitre_version: null) │ +(DRAFT RELEASE) │ + │ │ + ↓ │ +Ready for Tagging? │ + │ │ + ├─ NO ──────────────────────────┘ + │ (continue development) + │ + ├─ YES + ↓ +Tag Snapshot +(x_mitre_version: "1.0") +(snapshot tagged IN-PLACE) +(TAGGED RELEASE) + │ + ↓ +Tagged Release +(x_mitre_version: "1.0") + │ + ↓ +Continue Development ──────────────┐ + │ │ + ↓ │ +New Snapshot │ +(x_mitre_version: null) │ +(DRAFT RELEASE) │ + │ │ + ↓ │ +Tag Again │ +(x_mitre_version: "1.1") │ +(TAGGED RELEASE) │ + │ │ + └──────────────────────────────┘ +``` \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md b/docs/COLLECTIONS_V2/02_TERMINOLOGY.md new file mode 100644 index 00000000..44bdc850 --- /dev/null +++ b/docs/COLLECTIONS_V2/02_TERMINOLOGY.md @@ -0,0 +1,399 @@ +# Collections V2 - Terminology Guide + +## Overview + +This document defines the new terminology for Collections V2, replacing the overloaded term "collection" with a clearer vocabulary that distinguishes Workbench-specific concepts from STIX bundles, TAXII collections, MongoDB collections, and `x-mitre-collection` SDOs. + +## Problem Statement + +The term "collection" is heavily overloaded in the ATT&CK Workbench context: + +- **MongoDB Collection** - Database table/collection in MongoDB +- **TAXII Collection** - TAXII 2.1 specification concept for grouping STIX content +- **STIX Bundle** - A STIX 2.1 bundle (JSON object with type "bundle") +- **Collection Bundle** - A STIX bundle that contains an `x-mitre-collection` object +- **`x-mitre-collection` SDO** - A custom STIX Domain Object that acts as a table of contents +- **Collection Index** - Workbench-specific concept for subscribing to remote STIX bundles +- **Workbench Collection** (V1) - The existing Workbench concept being replaced + +This overloading creates confusion in documentation, code, and conversation. Collections V2 introduces new terminology to eliminate this ambiguity. + +--- + +## Core Terminology + +### Release Track + +A **release track** is a series/chain (linked list) of **snapshots**, where each snapshot is either a draft release or a tagged release. + +**Technical Definition:** +- A release track is represented by all documents in the database sharing the same `stix.id` +- Each document in the series represents a snapshot at a specific point in time +- The release track provides version control and release management for curated sets of STIX objects + +**Characteristics:** +- Has a unique identifier (e.g., `x-mitre-release--uuid`) + - **TODO**: should we support custom, user-specified identifiers? As long as we enforce uniqueness, I don't see an issue with abandoning the `type--uuid` format here, since the ID will not be exposed in releases. +- Contains a chronological history of all changes +- Supports Git-inspired versioning workflow + +**Types of Release Tracks:** + +There are two types of release tracks: + +1. **Standard Release Track** - Directly manages objects through workflow states (candidates → staged → released) + - Source of truth for specific objects + - Creates snapshots when objects are added/removed or metadata changes + - Examples: "GroupsMonthly", "TechniquesQuarterly" + +2. **Virtual Release Track** - Computes content by aggregating other release tracks + - No direct object management (objects managed in source tracks) + - Creates snapshots manually or on schedule + - Examples: "EnterpriseTwiceAnnual" (aggregates Groups + Techniques + Software) + +**Examples:** +- "Enterprise release track" (could be standard or virtual) +- "Mobile release track" (could be standard or virtual) +- "ICS release track" (could be standard or virtual) +- "Groups Monthly" (typically standard) +- "Techniques Quarterly" (typically standard) + +--- + +### Snapshot + +A **snapshot** is a *node* in the release track's version history, identified by its `stix.modified` timestamp. + +**Technical Definition:** +- Each snapshot is a MongoDB document with a specific `stix.id` and `stix.modified` timestamp +- **Snapshots are immutable** once created (**except** for tagging operations) +- The snapshot identifier is the combination of `stix.id` + `stix.modified` + +**Characteristics:** +- Unique `stix.modified` timestamp (ISO 8601 format) +- May be a **draft** release or a **tagged** release +- Contains the full state of the release track at that point in time +- Analogous to a Git commit + +**Types of Snapshots:** +1. **Draft Release** - Untagged snapshot, still in development +2. **Tagged Release** - Snapshot marked with a version number, considered published + +**Examples:** +- "Snapshot from 2024-01-15T16:20:00Z" +- "The latest snapshot in the Enterprise release track" +- "Show me all snapshots created in January" + +--- + +### Draft Release + +A **draft release** (or **draft snapshot**) is an untagged snapshot - still in development, not yet published. + +**Technical Definition:** +- A snapshot where `stix.x_mitre_version === null` +- Represents work-in-progress that has not been marked as production-ready +- Can be freely modified (creates new snapshots) without affecting published releases + +**Characteristics:** +- No version number assigned +- Not considered production-ready +- Can transition from draft to tagged state via tagging (in-place) operation +- May contain candidate, staged, and member objects in various states + +**Examples:** +- "The current draft release has 150 candidate objects" +- "Let's review the draft before tagging it" +- "This draft release includes updates to 50 techniques" + +--- + +### Tagged Release + +A **tagged release** (or **tagged snapshot**) is a snapshot that has been marked with a version number (`x_mitre_version`) and is considered published/released. + +**Technical Definition:** +- A snapshot where `stix.x_mitre_version !== null` +- The version follows MAJOR.MINOR format (e.g., "1.0", "2.3", "15.1") +- Created by performing a tagging operation on a draft release +- The `stix.modified` timestamp does not change during tagging (in-place operation) + +**Characteristics:** +- Has an explicit version number +- Considered production-ready and published +- **Immutable** - cannot be re-tagged or untagged +- Recorded in `workspace.version_history` for audit trail +- Analogous to a Git tag + +**Examples:** +- "Enterprise release track v14.1 is a tagged release" +- "Tag this snapshot as release v2.0" +- "Show me all tagged releases from 2024" +- "The latest tagged release contains 3,000 techniques" + +--- + +### Tagging Operation + +The **tagging operation** marks an existing snapshot as a tagged release by assigning it a version number. + +**Technical Definition:** +- Sets `stix.x_mitre_version` on an existing snapshot (in-place update) +- Does NOT create a new snapshot (does NOT change `stix.modified`) +- Adds an entry to `workspace.version_history` for audit trail +- Can be performed on the latest snapshot or a specific historical snapshot + +**Characteristics:** +- Analogous to `git tag` +- Version must be greater than all previous tagged releases (monotonically increasing) +- Cannot tag a snapshot that is already tagged (throws `AlreadyReleasedError`) +- Supports automatic version calculation (MAJOR/MINOR bump) or explicit version + +**Examples:** +- "Tag the latest snapshot as v1.5" +- "Tag snapshot from 2024-01-15 as v2.0" +- "Tagging operation failed - snapshot already tagged as v1.3" + +--- + +### Object Membership Tiers + +Every snapshot (draft or tagged) contains objects which can be classified into any of three categories: + +#### 1. Candidate Objects + +**Location:** `workspace.candidates` + +**Definition:** Objects being worked on; not yet ready for release. + +**Characteristics:** +- When an object is first added to a release track, is it considered a candidate. It does not have full membership yet; if the snapshot were to be tagged and released right now, candidates would not be included. +- Each entry can either be statically pinned to a specific version (via its `object_modified` timestamp), or dynamically pinned to the latest version. +- Each entry has a collection-scoped status: `work-in-progress`, `awaiting-review`, or `reviewed` +- Objects in this tier are NOT included in published STIX bundles by default +- Automatically promoted to staged tier when status reaches the candidacy threshold + +**Examples:** +- "Add these 10 techniques as candidate objects" +- "There are 47 candidate objects in work-in-progress status" +- "Transition candidate objects from awaiting-review to reviewed" + +#### 2. Staged Objects + +**Location:** `workspace.staged` + +**Definition:** Objects that have been reviewed (in this release track) and are ready for the next tagged release. + +**Characteristics:** +- Once a candidate's workflow status meets the release track's ["candidacy threshold"](./05_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `x_mitre_contents`. +- Each entry includes a version pin (`object_modified` timestamp) +- Auto-promoted from candidates when objects meet the candidacy threshold +- Moved to member objects tier (`x_mitre_contents`) when the snapshot is tagged +- NOT included in published STIX bundles until the snapshot is tagged + +**Examples:** +- "There are 12 staged objects ready for the next release" +- "Promote all reviewed candidates to staged" +- "Preview which staged objects will be included in the next tagged release" + +#### 3. Member Objects + +**Location:** `stix.x_mitre_contents` + +**Definition:** Objects included in the current/latest released version of this release track. + +**Characteristics:** +- Objects are considered "members" if they are "cooked" into the `x_mitre_contents` array of the current snapshot. These are considered already released. +- Each entry is a version-pinned reference (`object_ref` + `object_modified`) +- These objects are included in published STIX bundles +- Represents the production-ready, published content +- Only updated when a snapshot is tagged (staged objects are promoted to members) + +**Examples:** +- "The Enterprise release track has 3,247 member objects" +- "Export all member objects as a STIX bundle" +- "Which version of Technique T1234 is in the member objects?" + +--- + +### Virtual Release Track + +A **virtual release track** is a special type of release track that computes its contents by aggregating objects from other release tracks (called **component tracks**). + +**Technical Definition:** +- A virtual track is identified by `stix.type = "virtual"` in its schema +- Instead of managing objects directly, it defines **composition rules** that specify which tracks to aggregate and how +- Snapshots are created manually or on schedule by **resolving** the composition rules + +**Characteristics:** +- Does NOT manage objects through candidate/staged/released workflow +- Aggregates content from **component tracks** (standard or other virtual tracks) +- Only references **tagged snapshots** from component tracks (never drafts) +- Creates snapshots **manually** or **on schedule** (never event-driven) +- All snapshots start as drafts and must be explicitly tagged +- Can optionally have **native objects** in addition to composed content (hybrid model) + +**Examples:** +- "EnterpriseTwiceAnnual" virtual track aggregates: + - GroupsMonthly (latest tagged release) + - TechniquesQuarterly (latest tagged release) + - SoftwareBiannual (latest tagged release) +- "MobileQuarterly" virtual track aggregates: + - GroupsMobile (filtered to mobile domain) + - TechniquesMobile (filtered to mobile domain) + +--- + +### Component Track + +A **component track** is a release track (standard or virtual) that is referenced by a virtual release track. + +**Technical Definition:** +- A component track is specified in a virtual track's `workspace.composition.component_tracks` array +- Each component defines a `resolution_strategy` (how to select which snapshot to use) +- Each component can optionally specify `filters` (which objects to include) + +**Characteristics:** +- Component tracks are independent - they don't know they're being referenced +- Virtual tracks "pull" content from components via composition rules +- Components can be standard tracks (manage objects) or virtual tracks (aggregate) +- Components must have at least one tagged snapshot for virtual track to resolve + +**Examples:** +- "GroupsMonthly" is a component track of "EnterpriseTwiceAnnual" +- "TechniquesQuarterly" is a component track of "EnterpriseTwiceAnnual" + +--- + +### Composition + +**Composition** refers to the rules and configuration that define how a virtual release track aggregates content from component tracks. + +**Technical Definition:** +- Defined in `workspace.composition` object of virtual track +- Specifies which component tracks to include +- Defines resolution strategies, filters, and deduplication rules + +**Characteristics:** +- Composition is configuration, not data +- Resolved at snapshot creation time into concrete object references +- Can be updated, which creates a new draft snapshot with new composition + +**Example:** +```javascript +{ + component_tracks: [ + { + track_id: "GroupsMonthly--uuid", + resolution_strategy: "latest_tagged", + filters: { object_types: ["intrusion-set"] } + } + ], + deduplication: { + strategy: "prefer_latest_modified" + } +} +``` + +--- + +### Resolution + +**Resolution** is the process of converting a virtual track's composition rules into concrete object references. + +**Technical Definition:** +- Occurs when a virtual track snapshot is created +- For each component track, resolves to a specific snapshot based on strategy +- Collects all objects from resolved snapshots +- Applies filters and deduplication +- Produces immutable `composition_resolution` metadata + +**Characteristics:** +- Resolution happens at snapshot creation time (not query time) +- Resolution metadata is stored in snapshot (`workspace.composition_resolution`) +- Once resolved, a snapshot's composition is immutable +- Different snapshots of same virtual track may resolve to different component versions + +**Example:** + +``` +Virtual track "EnterpriseTwiceAnnual" has composition: + - GroupsMonthly: strategy = "latest_tagged" + +When snapshot created on March 1: + → Resolves to GroupsMonthly v5.2 (latest tagged on March 1) + +When snapshot created on July 1: + → Resolves to GroupsMonthly v5.8 (latest tagged on July 1) +``` + +--- + +### Resolution Strategy + +A **resolution strategy** determines which snapshot from a component track to use. + +**Options:** +1. **latest_tagged** - Use the most recent tagged snapshot from the component track +2. **specific_version** - Use a specific semantic version (e.g., "5.0") +3. **specific_snapshot** - Use a specific snapshot by timestamp + +**Examples:** +- `{ resolution_strategy: "latest_tagged" }` → Always gets latest +- `{ resolution_strategy: "specific_version", version: "5.0" }` → Always uses v5.0 +- `{ resolution_strategy: "specific_snapshot", snapshot: "2024-02-01T10:00:00Z" }` → Always uses that exact snapshot + +--- + +## Terminology Mapping + +### Old Term (V1) → New Term (V2) + +| Old Term (V1) | New Term (V2) | Notes | +|---------------|---------------|-------| +| Collection | Release Track | Top-level container concept | +| Collection version | Snapshot | Individual node in version history | +| Collection (unpublished) | Draft Release | Snapshot without version number | +| Collection (published) | Tagged Release | Snapshot with version number | +| Collection contents | Member objects | Objects in `stix.x_mitre_contents` | +| Collection Bundle | -- | STIX bundle exported from release track | + +### Technical Mappings + +| Concept | MongoDB Representation | +|---------|------------------------| +| Release Track | All docs with same `stix.id` | +| Snapshot | Doc with specific `stix.id` + `stix.modified` | +| Draft Release | Snapshot where `x_mitre_version === null` | +| Tagged Release | Snapshot where `x_mitre_version !== null` | +| Tagging Operation | Set `x_mitre_version` on existing doc | +| Candidate Objects | Array at `workspace.candidates` | +| Staged Objects | Array at `workspace.staged` | +| Member Objects | Array at `stix.x_mitre_contents` | + +--- + +## Conversational Usage + +**Development Workflow:** +- "Did you review Technique-A in the latest draft release?" +- "Add these techniques as candidates to the Enterprise release track" +- "Transition all candidate objects from work-in-progress to awaiting-review" +- "Which objects are staged for the next release?" + +**Release Management:** +- "Are we ready to tag this snapshot as a release?" +- "Tag the current draft as v14.1" +- "The Enterprise release track has 47 snapshots, 12 of which are tagged releases" +- "Show me all tagged releases from Q1 2024" + +**Version Control:** +- "Let's create a new release track for the space domain" +- "Compare snapshots from the Mobile and Enterprise release tracks" +- "Which snapshot introduced Technique T1234?" +- "Roll back to the previous tagged release" + +**Object Management:** +- "The current snapshot contains 3,000 member objects and 150 staged objects" +- "Move these candidate objects to staged" +- "Export the member objects as a STIX bundle" \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/03_VERSIONING.md b/docs/COLLECTIONS_V2/03_VERSIONING.md new file mode 100644 index 00000000..f59a31aa --- /dev/null +++ b/docs/COLLECTIONS_V2/03_VERSIONING.md @@ -0,0 +1,180 @@ +# Release Track Versioning and Release Process + +## Overview + +The Release Tracks API uses a Git-inspired versioning strategy that separates two distinct concerns: + +1. **Snapshot History** - Every modification creates a new timestamped snapshot for complete audit trail +2. **Release Versioning** - Specific snapshots can be "tagged" as releases using semantic versioning + +This approach allows continuous development while providing stable, versioned releases for publication. + +**Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](02_TERMINOLOGY.md) for the complete terminology guide. + +## Core Concepts + +### Snapshots + +A **snapshot** is an immutable state of a release track at a specific point in time, identified by: +- `stix.id` - The release track's STIX identifier (constant across all snapshots) +- `stix.modified` - ISO 8601 timestamp when the snapshot was created (unique per snapshot) + +Every modification operation creates a new snapshot with a new `stix.modified` timestamp. + +A snapshot may be either a **draft release** (untagged) or a **tagged release** (has version number). + +### Draft Releases vs Tagged Releases + +A **draft release** is a snapshot without a version number (`x_mitre_version === null`). It represents work-in-progress. + +A **tagged release** is a snapshot that has been marked as production-ready for publication, identified by: +- `stix.x_mitre_version` - Version string in MAJOR.MINOR format (e.g., "1.0") + +**Note:** ATT&CK release tracks use a two-part versioning scheme (MAJOR.MINOR), not the three-part semver format (MAJOR.MINOR.PATCH). The patch component is not tracked in `x_mitre_version`. + +Not all snapshots are tagged releases. Only snapshots explicitly tagged via the `tag` operation become tagged releases. + +**Example Timeline with Tagged Releases:** +``` +id: "release-track--123", snapshot_id: "2024-01-01T10:00:00.000Z" + version: null ← DRAFT RELEASE (work in progress) + +id: "release-track--123", snapshot_id: "2024-01-02T14:30:00.000Z" + version: null ← DRAFT RELEASE (work in progress) + +id: "release-track--123", snapshot_id: "2024-01-05T09:15:00.000Z" + version: "1.0" ← TAGGED RELEASE (via tagging operation) + version_history: [{ + version: "1.0", + tagged_at: "2024-01-05T10:00:00Z", + tagged_by: "user@example.com", + snapshot_id: "2024-01-05T09:15:00.000Z" + }] + +id: "release-track--123", snapshot_id: "2024-01-10T11:00:00.000Z" + version: null ← DRAFT RELEASE (more development) + +id: "release-track--123", snapshot_id: "2024-01-15T16:20:00.000Z" + version: "1.1" ← TAGGED RELEASE (via tagging operation) + version_history: [ + { version: "1.1", tagged_at: "2024-01-15T17:00:00Z", tagged_by: "user@example.com", snapshot_id: "2024-01-15T16:20:00.000Z" }, + { version: "1.0", tagged_at: "2024-01-05T10:00:00Z", tagged_by: "user@example.com", snapshot_id: "2024-01-05T09:15:00.000Z" } + ] +``` + +## The Tagging Operation + +### What is "Tagging"? + +The `tag` operation **tags an existing snapshot as a release** by assigning it a semantic version number (without the patch number). It does **NOT** create a new snapshot. + +This is analogous to Git's tagging system: +- Git commits = release track snapshots (identified by `stix.modified`) +- Git tags = tagged releases (identified by `stix.x_mitre_version`) + +### In-Place Tagging Strategy + +When you tag a snapshot: + +1. The **existing** snapshot is updated in-place +2. `version` is set to the new version +3. An entry is added to `version_history` for audit trail +4. The `modified` timestamp **does not change** + +**Why in-place?** +- Avoids duplicate data (no need to copy the entire release track) +- Clear semantics: tagging is metadata, not a content change +- Snapshots remain immutable except for the version tag +- Matches Git's model where tags point to existing commits + +### Tagging Endpoints + +#### Tag Latest Snapshot +``` +POST /api/release-tracks/:id/bump +``` + +Tags the most recent snapshot (highest `stix.modified`) as a tagged release. + +**Request Body (optional):** +```json +{ + "type": "major" | "minor", // Default: "minor" + "version": "2.0" // Alternative: explicit version (MAJOR.MINOR format) +} +``` + +**Examples:** + +1. **Automatic version calculation:** +```bash +# Current latest tagged release: 1.2 +# Tag as: 1.3 (minor increment) +POST /api/release-tracks/release--123/bump +{ + "type": "minor" +} +``` + +2. **Major version increment:** +```bash +# Current latest tagged release: 1.2 +# Tag as: 2.0 (major increment) +POST /api/release-tracks/release--123/bump +{ + "type": "major" +} +``` + +3. **Explicit version:** +```bash +# Set specific version (must be greater than previous) +POST /api/release-tracks/release--123/bump +{ + "version": "2.0" +} +``` + +4. **Default behavior (no body):** +```bash +# Defaults to minor increment +POST /api/release-tracks/release--123/bump +``` + +#### Tag Specific Snapshot +``` +POST /api/release-tracks/:id/snapshots/:modified/bump +``` + +Tags a specific snapshot as a tagged release. Can tag retroactively, (i.e., a non-latest snapshot can be tagged), granted no [versioning rules](#versioning-rules) are violated. + +**Use Cases:** +- You want to tag snapshot 3, then later also tag snapshot 5 +- You forgot to tag a snapshot and want to mark it retroactively +- You want to create multiple tagged releases from different development branches + +**Constraint:** The version must be greater than any previously tagged version (no semver regression). + +## Versioning Rules + +### Version Format + +Collections use a **two-part versioning scheme** (MAJOR.MINOR), inspired by semantic versioning but simplified for ATT&CK's release model: + +- **MAJOR** (`X.0`) - Significant releases with substantial changes, may include breaking changes +- **MINOR** (`X.Y`) - Incremental releases with additions, updates, or fixes + +**Note:** Unlike full semantic versioning (MAJOR.MINOR.PATCH), ATT&CK collections do not track patch versions. All changes, including bug fixes, increment the minor version or major version depending on significance. + +### Version Constraints + +1. **Monotonically increasing** - New versions must always be greater than previous versions +2. **Immutable once set** - Once a snapshot has `version` assigned, it cannot be changed +3. **Cannot re-tag** - A snapshot can only be tagged once (throws `AlreadyReleasedError` if attempted) +4. **Valid version format** - Must match `/^\d+\.\d+$/` (MAJOR.MINOR only, no patch component) + +### First Tagged Release + +For release tracks with no prior tagged releases: +- The first tag sets `version: "1.0"` (regardless of increment type) +- Or you can specify an explicit version like `"0.1"` \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md b/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md new file mode 100644 index 00000000..9354332f --- /dev/null +++ b/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md @@ -0,0 +1,1058 @@ +# Virtual Release Tracks + +## Overview + +Virtual release tracks are computed aggregations of standard release tracks. They provide a way to compose releases from multiple source tracks without duplicating object tracking, reducing mental overhead and storage requirements. + +**Key Characteristics:** +- Virtual tracks **compute** their contents from component standard tracks +- Only reference **tagged snapshots** from standard tracks (never drafts) +- Maintain their own **independent snapshot history and versioning** +- Create snapshots **manually or on schedule** (never event-driven) +- All snapshots start as **drafts** and must be explicitly tagged + +## Use Cases + +### Scenario 1: Different Cadences for Different Object Types + +``` +Standard Tracks (source of truth): + - GroupsMonthly: intrusion-set objects, releases monthly + - TechniquesQuarterly: attack-pattern objects, releases quarterly + - SoftwareBiannual: malware + tool objects, releases twice yearly + +Virtual Track (aggregation): + - EnterpriseTwiceAnnual: Aggregates all three, releases twice yearly +``` + +**Workflow:** +1. Each standard track releases independently on its own schedule +2. Enterprise virtual track snapshots twice yearly (Jan 1, July 1) +3. Each snapshot captures the **latest tagged release** from each component track +4. Enterprise team reviews snapshot, then tags it as a release + +**Benefit:** Groups can release 12 times/year while Enterprise releases 2 times/year, without tracking Groups in both places. + +### Scenario 2: Modular Content Organization + +``` +Standard Tracks: + - CoreTactics: All tactics + - CoreTechniques: All attack-patterns + - CoreGroups: All intrusion-sets + - CoreSoftware: All malware + tools + - CoreMitigations: All course-of-action objects + +Virtual Tracks: + - EnterpriseATT&CK: Aggregates all five + - MobileATT&CK: Aggregates relevant subsets with mobile domain filter + - ICSATT&CK: Aggregates relevant subsets with ICS domain filter +``` + +**Benefit:** Maintain objects by type in standard tracks, compose domain-specific releases as virtual tracks. + +## Virtual Track Types + +### Type: `virtual` + +Virtual tracks are identified by `stix.type = "virtual"` in their schema. + +```javascript +{ + stix: { + id: "x-mitre-collection--virtual-uuid", + type: "virtual", // Distinguishes from standard tracks + modified: "2024-03-01T10:00:00Z", + x_mitre_version: null, // Draft snapshot (or "14.0" if tagged) + name: "Enterprise ATT&CK", + description: "Virtual aggregation of Enterprise content" + }, + + workspace: { + // Composition rules (how to build this virtual track) + composition: { + component_tracks: [ + { + track_id: "GroupsMonthly--uuid", + resolution_strategy: "latest_tagged", + filters: { + object_types: ["intrusion-set"], + // Additional filters... + } + }, + { + track_id: "TechniquesQuarterly--uuid", + resolution_strategy: "latest_tagged", + filters: { + object_types: ["attack-pattern"] + } + } + ], + + deduplication: { + strategy: "prefer_latest_modified", + tier_resolution: "highest_tier", + status_resolution: "highest_status" + } + }, + + // Snapshot schedule configuration + snapshot_schedule: { + mode: "manual" | "cron" | "dates", + cron: "0 0 1 1,7 *", // Jan 1 and July 1 at midnight + dates: ["2024-01-01T00:00:00Z", "2024-07-01T00:00:00Z"] + }, + + // Optional: Virtual track can also have its own native objects + candidates: [], // Virtual track's own objects (not from components) + staged: [], + + config: { + candidacy_threshold: "reviewed", + auto_promote: true + }, + + version_history: [] + } +} +``` + +## Composition Resolution + +### Resolution Strategies + +#### 1. `latest_tagged` + +Always resolves to the most recent **tagged snapshot** from the component track. + +```javascript +{ + track_id: "GroupsMonthly--uuid", + resolution_strategy: "latest_tagged" +} + +// At virtual snapshot time (e.g., March 1, 2024): +// 1. Query GroupsMonthly for all snapshots where x_mitre_version !== null +// 2. Sort by stix.modified DESC +// 3. Take first result +// → Resolves to GroupsMonthly v5.2 (released Feb 15, 2024) +``` + +**Use case:** "Always include the latest Groups release in Enterprise" + +#### 2. `specific_version` + +Resolves to a specific semantic version from the component track. + +```javascript +{ + track_id: "GroupsMonthly--uuid", + resolution_strategy: "specific_version", + version: "5.0" +} + +// At virtual snapshot time: +// 1. Query GroupsMonthly for snapshot where x_mitre_version === "5.0" +// → Resolves to GroupsMonthly v5.0 (regardless of when snapshot occurs) +``` + +**Use case:** "Pin Enterprise to Groups v5.0 until we're ready to upgrade" + +#### 3. `specific_snapshot` + +Resolves to a specific snapshot by its `stix.modified` timestamp. + +```javascript +{ + track_id: "GroupsMonthly--uuid", + resolution_strategy: "specific_snapshot", + snapshot: "2024-02-01T10:00:00Z" +} + +// At virtual snapshot time: +// 1. Query GroupsMonthly for snapshot where stix.modified === "2024-02-01T10:00:00Z" +// → Resolves to that specific snapshot +``` + +**Use case:** "Lock to exact snapshot for reproducibility" + +### Filters + +Each component track can specify filters to limit which objects are included: + +```javascript +filters: { + // Only include specific object types + object_types: ["intrusion-set", "malware"], + + // Only include objects with specific domains (if applicable) + domains: ["enterprise", "mobile"], + + // Only include objects matching STIX filter pattern (advanced) + stix_pattern: { + "x_mitre_platforms": { "$in": ["Windows", "macOS"] } + } +} +``` + +### Deduplication Rules + +When multiple component tracks contain the same object: + +```javascript +deduplication: { + // Which version of the object to include + strategy: "prefer_latest_modified" | "prefer_highest_version" | "error", + + // If object is in different tiers across components, which tier wins? + tier_resolution: "highest_tier" | "source_priority", + + // If object has different statuses across components, which status wins? + status_resolution: "highest_status" | "source_priority" +} +``` + +**Example:** + +```javascript +// GroupsMonthly has: intrusion-set--APT1 +// - modified: 2024-02-01 +// - tier: members (released) +// - status: reviewed + +// MobileGroups has: intrusion-set--APT1 +// - modified: 2024-01-15 +// - tier: candidates +// - status: work-in-progress + +// Virtual track with deduplication: +{ + strategy: "prefer_latest_modified", // → Use 2024-02-01 from GroupsMonthly + tier_resolution: "highest_tier", // → Use "members" tier + status_resolution: "highest_status" // → Use "reviewed" status +} +``` + +## Virtual Track Snapshot Lifecycle + +### 1. Snapshot Creation + +Virtual track snapshots are created either **manually** or **on schedule**. + +#### Manual Snapshot + +```bash +POST /api/release-tracks/:id/snapshots/create +``` + +**Request:** +```json +{ + "description": "Q1 2024 Enterprise snapshot" +} +``` + +**Response:** +```json +{ + "stix": { + "id": "x-mitre-collection--virtual-uuid", + "modified": "2024-03-01T10:00:00Z", + "x_mitre_version": null, // Draft snapshot + "type": "virtual" + }, + + "composition_resolution": { + "resolved_at": "2024-03-01T10:00:00Z", + "component_snapshots": [ + { + "track_id": "GroupsMonthly--uuid", + "track_name": "Groups Monthly", + "resolved_snapshot": "2024-02-15T10:00:00Z", + "resolved_version": "5.2", + "strategy_used": "latest_tagged", + "object_count": 47 + }, + { + "track_id": "TechniquesQuarterly--uuid", + "track_name": "Techniques Quarterly", + "resolved_snapshot": "2024-01-15T10:00:00Z", + "resolved_version": "2.1", + "strategy_used": "latest_tagged", + "object_count": 823 + } + ], + "total_objects": 870, + "duplicates_resolved": 0 + } +} +``` + +**Business Logic:** +1. For each component track in `composition.component_tracks`: + - Resolve snapshot based on `resolution_strategy` + - **Validate that resolved snapshot is tagged** (x_mitre_version !== null) + - Apply `filters` to get subset of objects + - Collect all object references +2. Apply deduplication rules across all components +3. Create new virtual track snapshot with: + - New `stix.modified` timestamp + - `x_mitre_version = null` (always starts as draft) + - `composition_resolution` metadata (what was included) +4. Return snapshot with resolution details + +#### Scheduled Snapshot + +Virtual tracks can be configured to auto-generate snapshots on a schedule: + +```javascript +snapshot_schedule: { + mode: "cron", + cron: "0 0 1 1,7 *" // Jan 1 and July 1 at midnight +} +``` + +**Scheduler integration:** +```javascript +scheduler.register({ + type: "virtual-track-snapshot", + trackId: "EnterpriseTwiceAnnual--uuid", + schedule: "0 0 1 1,7 *", + handler: async (trackId) => { + await virtualTrackService.createSnapshot(trackId, { + description: `Scheduled snapshot ${new Date().toISOString()}` + }); + + // Optionally notify team + await notificationService.send({ + to: "enterprise-team@example.com", + subject: "Enterprise ATT&CK snapshot created", + body: "A new draft snapshot is ready for review and tagging" + }); + } +}); +``` + +### 2. Snapshot Review + +Before tagging, team reviews the draft snapshot: + +```bash +GET /api/release-tracks/:id/snapshots/:modified?format=workbench&include=all +``` + +**Response includes:** +- All objects that will be in the release +- Composition resolution details (which component versions were used) +- Statistics and diff from previous tagged release + +### 3. Snapshot Tagging + +Once reviewed, explicitly tag the draft snapshot: + +```bash +POST /api/release-tracks/:id/snapshots/:modified/bump +``` + +**Request:** +```json +{ + "type": "major", // or "minor", or explicit "version": "14.0" +} +``` + +**Response:** +```json +{ + "stix": { + "id": "x-mitre-collection--virtual-uuid", + "modified": "2024-03-01T10:00:00Z", // Unchanged + "x_mitre_version": "14.0", // Now tagged + "type": "virtual" + }, + + "composition_resolution": { + // Preserved from snapshot creation + "resolved_at": "2024-03-01T10:00:00Z", + "component_snapshots": [...] + }, + + "version_history": [ + { + "version": "14.0", + "tagged_at": "2024-03-05T14:00:00Z", // Later than snapshot creation + "tagged_by": "admin@example.com", + "snapshot_created_at": "2024-03-01T10:00:00Z", + "component_versions": { + "GroupsMonthly": "5.2", + "TechniquesQuarterly": "2.1" + } + } + ] +} +``` + +**Business Logic:** +1. Validate snapshot exists and is a draft (x_mitre_version === null) +2. Calculate/validate version number +3. Set x_mitre_version on snapshot (in-place update) +4. Add entry to workspace.version_history +5. Snapshot is now immutable + +### 4. Snapshot Export + +Export virtual track snapshot as STIX bundle: + +```bash +GET /api/release-tracks/:id/snapshots/:modified?format=bundle +``` + +**Response:** +```json +{ + "type": "bundle", + "id": "bundle--uuid", + "objects": [ + { + "type": "x-mitre-collection", + "id": "x-mitre-collection--virtual-uuid", + "modified": "2024-03-01T10:00:00Z", + "x_mitre_version": "14.0", + "name": "Enterprise ATT&CK", + "x_mitre_contents": [ + "intrusion-set--APT1", + "intrusion-set--APT2", + "attack-pattern--T1234", + // ... all 870 objects + ] + }, + // ... all 870 actual STIX objects + ] +} +``` + +**Note:** The exported bundle is **materialized** - it contains concrete object references, not composition metadata. Consumers see a standard STIX bundle, unaware it came from a virtual track. + +## Composition Resolution Details + +### Resolution Metadata + +Each virtual track snapshot stores metadata about how it was composed: + +```javascript +{ + stix: { + modified: "2024-03-01T10:00:00Z", + x_mitre_version: "14.0" + }, + + workspace: { + composition_resolution: { + resolved_at: "2024-03-01T10:00:00Z", + + component_snapshots: [ + { + track_id: "GroupsMonthly--uuid", + track_name: "Groups Monthly", + track_type: "standard", + + // Which snapshot was used + resolved_snapshot: "2024-02-15T10:00:00Z", + resolved_version: "5.2", + + // How it was resolved + strategy_used: "latest_tagged", + filters_applied: { + object_types: ["intrusion-set"] + }, + + // Statistics + total_objects_in_source: 47, + objects_after_filter: 47, + objects_contributed: 47 // After deduplication + } + ], + + // Deduplication report + deduplication: { + total_objects_before: 870, + total_objects_after: 870, + duplicates_found: 0, + conflicts_resolved: [] + }, + + // Native objects (if any) + native_objects: { + candidates_count: 0, + staged_count: 0, + members_count: 0 + }, + + // Final statistics + summary: { + total_objects: 870, + by_type: { + "intrusion-set": 47, + "attack-pattern": 823 + }, + by_tier: { + "members": 870, + "staged": 0, + "candidates": 0 + } + } + } + } +} +``` + +### Validation Rules + +#### 1. Component tracks must have tagged snapshots + +```javascript +// When creating virtual snapshot +for (const component of composition.component_tracks) { + const snapshot = await resolveSnapshot(component); + + if (snapshot.stix.x_mitre_version === null) { + throw new ValidationError( + `Component track ${component.track_id} resolved to draft snapshot. ` + + `Virtual tracks can only reference tagged snapshots.` + ); + } +} +``` + +**User experience:** +```bash +POST /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/create + +# Error response: +{ + "error": "ValidationError", + "message": "Cannot create virtual snapshot: component track 'GroupsMonthly' has no tagged releases", + "details": { + "component": "GroupsMonthly--uuid", + "issue": "No tagged snapshots found (all snapshots are drafts)" + } +} +``` + +#### 2. Circular dependency prevention + +```javascript +// When creating/updating virtual track composition +async function validateNoCycles(virtualTrack) { + const visited = new Set(); + const stack = [virtualTrack.stix.id]; + + while (stack.length > 0) { + const currentId = stack.pop(); + + if (visited.has(currentId)) { + throw new ValidationError(`Circular dependency detected: ${currentId}`); + } + + visited.add(currentId); + + const track = await getReleaseTrack(currentId); + + if (track.stix.type === "virtual") { + for (const component of track.workspace.composition.component_tracks) { + stack.push(component.track_id); + } + } + } +} +``` + +#### 3. Maximum composition depth + +```javascript +workspace: { + config: { + max_composition_depth: 3 // Limit nesting to prevent performance issues + } +} +``` + +Virtual track nesting example: +``` +VirtualA (depth 0) + → VirtualB (depth 1) + → VirtualC (depth 2) + → StandardD (depth 3) ✓ allowed + → StandardE (depth 4) ✗ exceeds max_composition_depth +``` + +## API Reference + +### Create Virtual Track + +```bash +POST /api/release-tracks/new +``` + +**Request:** +```json +{ + "type": "virtual", + "name": "Enterprise ATT&CK", + "description": "Virtual aggregation of Enterprise content", + + "composition": { + "component_tracks": [ + { + "track_id": "GroupsMonthly--uuid", + "resolution_strategy": "latest_tagged", + "filters": { + "object_types": ["intrusion-set"] + } + } + ], + "deduplication": { + "strategy": "prefer_latest_modified", + "tier_resolution": "highest_tier", + "status_resolution": "highest_status" + } + }, + + "snapshot_schedule": { + "mode": "cron", + "cron": "0 0 1 1,7 *" + } +} +``` + +### Update Composition + +```bash +PUT /api/release-tracks/:id/composition +``` + +**Request:** +```json +{ + "component_tracks": [ + { + "track_id": "GroupsMonthly--uuid", + "resolution_strategy": "latest_tagged" + }, + { + "track_id": "TechniquesQuarterly--uuid", + "resolution_strategy": "specific_version", + "version": "2.0" + } + ] +} +``` + +**Note:** Updating composition creates a new draft snapshot with the new composition rules. + +### Create Virtual Snapshot + +```bash +POST /api/release-tracks/:id/snapshots/create +``` + +**Request:** +```json +{ + "description": "Q1 2024 snapshot" +} +``` + +### Preview Virtual Snapshot + +Preview what a snapshot would contain without creating it: + +```bash +GET /api/release-tracks/:id/snapshots/preview +``` + +**Response:** +```json +{ + "preview": { + "would_resolve_to": { + "component_snapshots": [...], + "total_objects": 870 + }, + "comparison_to_latest_tagged": { + "current_version": "13.1", + "new_objects": 12, + "updated_objects": 45, + "removed_objects": 3 + } + } +} +``` + +### Tag Virtual Snapshot + +```bash +POST /api/release-tracks/:id/snapshots/:modified/bump +``` + +**Request:** +```json +{ + "type": "major" +} +``` + +### Get Virtual Track with Resolved Content + +```bash +GET /api/release-tracks/:id?format=workbench&include=all +``` + +**Query params:** +- `format`: `bundle` | `workbench` | `filesystemstore` +- `include`: `members` | `staged` | `candidates` | `all` +- `resolve`: `true` (default) | `false` - Whether to resolve composition + +**Response when `resolve=true`:** +```json +{ + "stix": { + "id": "x-mitre-collection--virtual-uuid", + "type": "virtual", + "x_mitre_version": null + }, + + "resolved_content": { + "members": [ + { + "object_ref": "intrusion-set--APT1", + "object_modified": "2024-02-01T10:00:00Z", + "source_track": "GroupsMonthly--uuid", + "source_version": "5.2" + } + // ... all resolved objects + ], + "staged": [], + "candidates": [] + }, + + "composition_resolution": { + "resolved_at": "2024-03-05T10:00:00Z", + "component_snapshots": [...] + } +} +``` + +## Hybrid Model: Virtual Track + Native Objects + +Virtual tracks can have **both** composed content **and** native objects: + +```javascript +{ + stix: { + type: "virtual" + }, + + workspace: { + // Composed from standard tracks + composition: { + component_tracks: [ + { track_id: "GroupsMonthly--uuid" }, + { track_id: "TechniquesQuarterly--uuid" } + ] + }, + + // PLUS virtual track's own candidates/staged + candidates: [ + { + object_ref: "marking-definition--enterprise-only", + object_modified: "2024-01-01T10:00:00Z", + status: "reviewed" + } + ], + + staged: [] + } +} +``` + +**Use case:** Enterprise track includes Groups and Techniques from standard tracks, PLUS Enterprise-specific marking definitions or custom objects. + +**When virtual snapshot is created:** +1. Resolve composed content from component tracks +2. Merge with virtual track's native candidates/staged/members +3. Apply deduplication if any native objects overlap with composed objects + +## Migration Strategy + +### Phase 1: Create Standard Tracks + +```bash +# Create standard tracks for each object type +POST /api/release-tracks/new +{ + "name": "Groups Monthly", + "description": "All intrusion-set objects" +} + +# Add existing Groups as candidates +POST /api/release-tracks/GroupsMonthly--uuid/candidates +{ + "object_refs": ["intrusion-set--APT1", "intrusion-set--APT2", ...] +} + +# Tag initial release +POST /api/release-tracks/GroupsMonthly--uuid/bump +{ "version": "1.0" } +``` + +### Phase 2: Create Virtual Track + +```bash +POST /api/release-tracks/new +{ + "type": "virtual", + "name": "Enterprise ATT&CK", + "composition": { + "component_tracks": [ + { + "track_id": "GroupsMonthly--uuid", + "resolution_strategy": "latest_tagged" + }, + { + "track_id": "TechniquesQuarterly--uuid", + "resolution_strategy": "latest_tagged" + } + ] + }, + "snapshot_schedule": { + "mode": "dates", + "dates": ["2024-07-01T00:00:00Z", "2025-01-01T00:00:00Z"] + } +} +``` + +### Phase 3: Create First Virtual Snapshot + +```bash +# Manually trigger first snapshot +POST /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/create + +# Review draft snapshot +GET /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/:modified + +# Tag as Enterprise v14.0 +POST /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/:modified/bump +{ "version": "14.0" } +``` + +### Phase 4: Ongoing Workflow + +``` +Timeline: + +Jan 15: GroupsMonthly releases v1.1 (updated Groups) +Feb 15: GroupsMonthly releases v1.2 (more updates) +Mar 15: TechniquesQuarterly releases v2.1 (updated Techniques) +Apr 15: GroupsMonthly releases v1.3 + +July 1: Enterprise scheduled snapshot triggers + → Resolves GroupsMonthly v1.3 (latest tagged) + → Resolves TechniquesQuarterly v2.1 (latest tagged) + → Creates draft snapshot + +July 5: Team reviews draft, tags as Enterprise v14.1 +``` + +## Performance Optimizations + +### 1. Snapshot Caching + +Since virtual snapshots are immutable once created, cache resolved content: + +```javascript +const cacheKey = `virtual-snapshot:${trackId}:${modified}:resolved`; + +const cached = await cache.get(cacheKey); +if (cached) return cached; + +const resolved = await resolveVirtualSnapshot(trackId, modified); +await cache.set(cacheKey, resolved, { ttl: 3600 }); // 1 hour cache +``` + +### 2. Lazy Resolution + +For `GET /api/release-tracks/:id` (latest snapshot), only resolve if: +- Query param `resolve=true` is specified +- Format requires resolution (e.g., `format=bundle`) + +Otherwise, return composition metadata without resolving: + +```javascript +if (!query.resolve && query.format === 'workbench') { + // Return composition config without resolving + return { + stix: snapshot.stix, + workspace: { + composition: snapshot.workspace.composition, + composition_resolution: snapshot.workspace.composition_resolution // Pre-computed + } + }; +} +``` + +### 3. Parallel Component Resolution + +Resolve component tracks in parallel: + +```javascript +const resolutions = await Promise.all( + composition.component_tracks.map(async (component) => { + return await resolveComponentSnapshot(component); + }) +); +``` + +### 4. Deduplication Optimization + +Use Set for O(1) duplicate detection: + +```javascript +const seen = new Set(); +const deduplicated = []; + +for (const obj of allObjects) { + const key = `${obj.object_ref}:${obj.object_modified}`; + if (!seen.has(key)) { + seen.add(key); + deduplicated.push(obj); + } +} +``` + +## Best Practices + +### 1. Snapshot Before Tagging + +Always create snapshot, review, then tag: + +```bash +# Create draft +POST /api/release-tracks/:id/snapshots/create + +# Review +GET /api/release-tracks/:id/snapshots/:modified?format=workbench + +# Preview export +GET /api/release-tracks/:id/snapshots/:modified?format=bundle + +# Tag only when satisfied +POST /api/release-tracks/:id/snapshots/:modified/bump +``` + +### 2. Use Scheduled Snapshots for Consistency + +Define snapshot schedule up front: + +```javascript +snapshot_schedule: { + mode: "dates", + dates: [ + "2024-01-15T00:00:00Z", + "2024-07-15T00:00:00Z", + "2025-01-15T00:00:00Z" + ] +} +``` + +### 3. Document Component Versions + +Add metadata to virtual track for documentation: + +```javascript +{ + description: "Enterprise ATT&CK v14.0 includes:\n" + + "- Groups Monthly v1.3 (47 Groups)\n" + + "- Techniques Quarterly v2.1 (823 Techniques)\n" + + "- Software Biannual v1.0 (450 Software)" +} +``` + +### 4. Monitor Component Track Releases + +Set up alerts when component tracks release: + +```javascript +eventBus.on('release-track:released', async (event) => { + // Find virtual tracks that reference this standard track + const virtualTracks = await findVirtualTracksByComponent(event.collectionId); + + // Notify virtual track owners + for (const vt of virtualTracks) { + await notificationService.send({ + to: vt.owner_email, + subject: `Component track ${event.collectionName} released v${event.version}`, + body: `Your virtual track "${vt.name}" references this component. ` + + `Consider creating a new snapshot to include the latest release.` + }); + } +}); +``` + +## Limitations + +### 1. No Event-Driven Snapshots + +Virtual tracks do NOT automatically snapshot when component tracks release. + +**Rationale:** Prevents snapshot explosion when many component tracks release frequently. + +**Alternative:** Use notifications + manual snapshots, or scheduled snapshots. + +### 2. No Workflow on Composed Objects + +Virtual tracks cannot transition workflow status of composed objects. + +**Rationale:** Composed objects are owned by standard tracks; virtual tracks are read-only views. + +**Alternative:** If you need to change object status, do it in the source standard track. + +### 3. Only Reference Tagged Snapshots + +Virtual tracks cannot compose from draft snapshots. + +**Rationale:** Ensures stability and prevents virtual snapshots from inadvertently including WIP content. + +**Alternative:** Tag the standard track snapshot first, then create virtual snapshot. + +## Error Handling + +### Error: Component Has No Tagged Snapshots + +```json +{ + "error": "NoTaggedSnapshotsError", + "message": "Component track 'GroupsMonthly' has no tagged releases", + "resolution": "Tag at least one snapshot in the component track before creating virtual snapshot" +} +``` + +### Error: Circular Dependency + +```json +{ + "error": "CircularDependencyError", + "message": "Virtual track composition creates circular dependency: VirtualA → VirtualB → VirtualA", + "resolution": "Remove one of the component track references to break the cycle" +} +``` + +### Error: Composition Depth Exceeded + +```json +{ + "error": "CompositionDepthExceededError", + "message": "Virtual track composition exceeds maximum depth of 3", + "resolution": "Reduce nesting of virtual tracks" +} +``` diff --git a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md new file mode 100644 index 00000000..8b29134f --- /dev/null +++ b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md @@ -0,0 +1,871 @@ +# Release Workflow + +## Overview + +This document describes how object workflow states integrate with the release track versioning and release system. It addresses the critical challenge of managing thousands of objects being developed in parallel by multiple users while maintaining clean, production-ready tagged releases. + +**Key Design Decision:** This system uses **release track-centric status with version pinning** to solve the "STIX freeze" problem. Each release track tracks its own workflow status for objects and pins to specific object versions, allowing the same object to be in different states across different release tracks and enabling work on future releases while current releases are frozen. + +**Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) for the complete terminology guide. + +## Core Concepts + +### Object Workflow States (Release Track-Centric) + +Workflow status is **scoped to each release track**, not global to the object. This means: +- The same object can have different workflow states in different release tracks +- Release Track A can track an object as "reviewed" while Release Track B tracks it as "work-in-progress" +- Each release track independently manages which objects are ready for release + +The three workflow states tracked per release track: +1. **work-in-progress** - Object is being actively developed, not ready for review +2. **awaiting-review** - Object is complete and waiting for team review +3. **reviewed** - Object has been reviewed and approved, ready for release + +### Version Pinning + +Each tier entry includes **version pinning** via the `object_modified` timestamp: +- Release tracks track a reference to a **specific version** of an object (identified by its `stix.modified` timestamp) +- Different release tracks can pin to different versions of the same object +- This enables working on future object versions while a tagged release containing an earlier version is frozen + +### Release Track Membership Tiers + +Release tracks maintain objects in three distinct tiers, with each entry pinning to a specific object version: + +1. **Candidates** (`workspace.candidates`) - Objects being worked on with track-scoped status +2. **Staged** (`workspace.staged`) - Reviewed objects (in this release track) ready for the next tagged release +3. **Released** (`stix.x_mitre_contents`) - Object versions included in the current/latest tagged release + +### Automatic Promotion Flow + +``` +Object version added to release track + ↓ +Track-scoped status: work-in-progress → Added to workspace.candidates with version pin + ↓ +Track-scoped status: awaiting-review → Remains in workspace.candidates + ↓ +Track-scoped status: reviewed → Automatically promoted to workspace.staged + ↓ +Snapshot tagged → workspace.staged entries moved to stix.x_mitre_contents +``` + +### STIX Freeze Solution + +Version pinning solves the "STIX freeze" problem: + +**The Problem:** +- User completes changes to Object A for Release Track A's next tagged release +- Cannot start working on Object A for the next-next release until after current release ships +- Must wait for STIX freeze to end before continuing development + +**The Solution:** +- Release Track A pins to `attack-pattern--A, modified: 2024-01-15T10:00:00Z` for the v1.5 tagged release +- User creates new version of Object A: `attack-pattern--A, modified: 2024-01-20T14:00:00Z` +- New version can be added to Release Track A as a candidate for the NEXT release (v1.6) +- Release Track A now tracks TWO versions of the same object: + - Released tier: `modified: 2024-01-15T10:00:00Z` (frozen for v1.5) + - Candidates tier: `modified: 2024-01-20T14:00:00Z` (in development for v1.6) +- Work continues on v1.6 while v1.5 is frozen + +## Candidacy Threshold Configuration + +Release tracks can be configured with different thresholds for what workflow states are acceptable. This controls how the "auto-promotion" mechanism works; if an object status meets the candidacy threshold, then the object will be automatically staged for the next release. + +Typical release tracks will use the default candidacy threshold setting of `reviewed`, which requires that the object(s) status be `reviewed` in order for the object to become staged. + +However, smaller teams operating in purely developmenet or research capacities may prefer a more permissive model. Perhaps they simply want all objects to be included in the release irrespective of object status. In such situations, the candidacy threshold can be lowered to `awaiting-review` or `work-in-progress`. + +### Option 1: Include Only Reviewed (Default) +```javascript +workspace.config.candidacy_threshold = "reviewed" +``` +- Only objects with `status: "reviewed"` are auto-promoted to staged +- Strictest option for production collections + +### Option 2: Include Awaiting Review +```javascript +workspace.config.candidacy_threshold = "awaiting-review" +``` +- Objects with `status: "awaiting-review"` or `"reviewed"` are auto-promoted to staged +- Good for collections with trusted contributors + +### Option 3: Include Work in Progress +```javascript +workspace.config.candidacy_threshold = "work-in-progress" +``` +- All objects are immediately promoted to staged +- Useful for development/testing collections +- No filtering based on workflow status + +## Workflow Operations + +### 1. Adding Objects as Candidates (with Version Pinning) + +Candidates can be added or modified with the following endpoint: +``` +POST /api/release-tracks/:id/candidates +``` + +**Request Body:** +```json +{ + "object_refs": [ + { + "id": "attack-pattern--eee", + "modified": "2024-01-12T09:00:00Z" // Optional: pin to specific version, defaults to latest + }, + { + "id": "attack-pattern--fff" // No modified = use latest version + } + ] +} +``` + +**Simplified Request Body (defaults to latest version):** +```json +{ + "object_refs": ["attack-pattern--eee", "attack-pattern--fff"] +} +``` + +**Response:** +```json +{ + "added": [ + { + "object_ref": "attack-pattern--eee", + "object_modified": "2024-01-12T09:00:00Z", + "status": "work-in-progress", + "added_to": "candidates" + }, + { + "object_ref": "attack-pattern--fff", + "object_modified": "2024-01-13T14:00:00Z", + "status": "work-in-progress", + "added_to": "staged" // Auto-promoted if meets threshold + } + ], + "errors": [] +} +``` + +**Business Logic:** +1. Validate all object_refs exist +2. Resolve `object_modified` timestamp: + - If provided: validate that specific version exists + - If omitted: use latest version (highest `stix.modified`) +3. Set initial track-scoped status (defaults to "work-in-progress") +4. Add to `workspace.candidates` with version pin +5. If status meets `candidacy_threshold`, auto-promote to `workspace.staged` +6. Update object's `workspace.referenced_by` array + +Importantly, candidate removal/deletion must occur separately using the `DELETE` operation: +```bash +DELETE /api/release-tracks/:id/candidates +``` + +### 2. Bulk Workflow Status Change + +Update the workflow status of many candidates at once using one bulk operation. Target specific candidates using `object_refs` or all objects by omitting. +``` +POST /api/release-tracks/:id/candidates/Review +``` + +**Request Body:** +```json +{ + "from": "work-in-progress", + "to": "awaiting-review", + "object_refs": ["attack-pattern--eee"] // optional, transitions all if omitted +} +``` + +**Response:** +```json +{ + "transitioned": [ + { + "object_ref": "attack-pattern--eee", + "object_modified": "2024-01-12T09:00:00Z", + "old_status": "work-in-progress", + "new_status": "awaiting-review", + "promoted_to_staged": false // Doesn't meet threshold yet + } + ], + "errors": [] +} +``` + +**Business Logic:** +1. Find all entries in `workspace.candidates` matching `from` status +2. Filter by `object_refs` if provided +3. Update track-scoped status for each entry from `from` to `to` +4. If new status meets `candidacy_threshold`, promote entry to `workspace.staged` +5. Update object's `workspace.referenced_by` to reflect new status +6. Fire `collection:status-changed` event for each object +7. Return summary of transitions + +**Important:** This only affects status within THIS collection. The same object may have different statuses in other collections. + +### 3. Manual Promotion to Staged + +Editors may bypass auto-promotion (via candidacy threshold) by *manually* promoting candidates to the staged status. Importantly, the workflow status will remain unchanged which may be in conflict with the release track's candidacy threshold setting. Realistic use cases include situations where WIP objects need to be rushed out the door in some imminent release before a reviewer has time to officially review and update its workflow status accordingly. +``` +POST /api/release-tracks/:id/candidates/promote +``` + +**Request Body:** +```json +{ + "object_refs": ["attack-pattern--eee"] +} +``` + +**Response:** +```json +{ + "promoted": [ + { + "object_ref": "attack-pattern--eee", + "status": "work-in-progress", + "warning": "Object is not reviewed, manual override applied" + } + ] +} +``` + +**Business Logic:** +- Allows manual promotion even if status doesn't meet threshold +- Useful for exceptions or urgent fixes +- Logs warning for audit trail + +### 4. Viewing Latest Snapshot with All Tiers + +Set the `include` query parameter to `members`, `staged`, `candidates` or `all` to view different subsets of a given snapshot. +``` +GET /api/release-tracks/:id?include=all +``` + +**Response:** +```json +{ + "id": "release-track--123", + "version": "1.1", + "members": [ + { + "ref": "attack-pattern--aaa", + "modified": "2024-01-10T10:00:00Z" + }, + { + "ref": "malware--bbb", + "modified": "2024-01-11T14:30:00Z" + } + ], + "staged": [ + { + "ref": "attack-pattern--ddd", + "modified": "2024-01-14T10:00:00Z", + "status": "reviewed", + "object_name": "New Technique XYZ" + } + ], + "candidates": [ + { + "ref": "attack-pattern--eee", + "modified": "2024-01-12T09:00:00Z", + "status": "work-in-progress", + "object_name": "WIP Technique" + } + ], + "summary": { + "members_count": 2, + "staged_count": 1, + "candidate_count": 1, + "total_count": 4 + } +} +``` + +### 5. Preview Release + +Compute a release preview, which outputs a verbose diff of what will change in the next release. +``` +GET /api/release-tracks/:id/bump/preview +``` + +**Response:** +```json +{ + "current_version": "1.1", + "next_version": "1.2", + "release_preview": { + "will_include": [ + { + "ref": "attack-pattern--aaa", + "modified": "2024-01-10T10:00:00Z", + "object_type": "attack-pattern", + "name": "Technique A", + "status": "reviewed", + "source": "members" + }, + { + "ref": "attack-pattern--ddd", + "modified": "2024-01-14T10:00:00Z", + "object_type": "attack-pattern", + "name": "New Technique XYZ", + "status": "reviewed", + "source": "staged" + } + ], + "will_exclude": [ + { + "ref": "attack-pattern--eee", + "modified": "2024-01-12T09:00:00Z", + "object_type": "attack-pattern", + "name": "WIP Technique", + "status": "work-in-progress", + "reason": "Object is work-in-progress, not meeting candidacy threshold" + } + ] + }, + "statistics": { + "total_objects": 3, + "included_objects": 2, + "excluded_objects": 1 + } +} +``` + +### 6. Bump with Staging + +``` +POST /api/collections/:id/bump +``` + +**Request:** +```json +{ + "type": "minor", + "dry_run": false // <-- optionally perform a dry run to preview the next release4 +} +``` + +**Response:** +```json +{ + "id": "release-track--123", + "snapshot_id": "2024-01-15T16:20:00.000Z", + "modified": "2024-01-15T16:20:00Z", + "version": "1.2", + "members": [ + { + "ref": "attack-pattern--aaa", + "modified": "2024-01-10T10:00:00Z" + }, + { + "ref": "malware--bbb", + "modified": "2024-01-11T14:30:00Z" + }, + { + "ref": "attack-pattern--ddd", + "modified": "2024-01-14T10:00:00Z" // Promoted from staged + } + ], + "release_summary": { + "promoted_from_staged": [ + { + "ref": "attack-pattern--ddd", + "modified": "2024-01-14T10:00:00Z", + "name": "New Technique XYZ" + } + ], + "remaining_staged": [], + "remaining_candidates": [ + { + "ref": "attack-pattern--eee", + "modified": "2024-01-12T09:00:00Z", + "name": "WIP Technique", + "status": "work-in-progress" + } + ] + } +} +``` + +**Business Logic:** +1. Validate no `AlreadyReleasedError` +2. Calculate next version +3. Move all entries from `staged` to `members` (preserving version pins) +4. Update object documents: change tier in `workspace.referenced_by` from "staged" → "members" +5. Set `version` on release track +6. Add entry to `version_history` +7. Return summary showing what was promoted + +**Note on Version Pins:** The `modified` timestamps are preserved during promotion. Released objects remain pinned to the specific version that was reviewed and staged. + +## Solving the STIX Freeze Problem + +### The Problem in Detail + +Workbench was originally designed such that objects have a single global status. When preparing a release: +1. Object A is marked "reviewed" and frozen for the v1.5 release +2. Release process begins (can take days or weeks) +3. During this freeze period, developers **cannot** work on Object A for the v1.6 release +4. Must wait for v1.5 release to complete before resuming work +5. This creates significant workflow bottlenecks + +### The Solution: Version Pinning + Collection-Scoped Status + +With version pinning, collections track specific object versions: + +**Step-by-Step Example:** + +```bash +# 1. Initial state: Collection v1.4 released +GET /api/release-tracks/release-track--enterprise +# Response shows: +# - version: "1.4" +# - members includes: attack-pattern--T1234, modified: 2024-01-01T10:00:00Z + +# 2. Developer updates T1234 for v1.5 release +POST /api/objects/attack-pattern--T1234 +{ "description": "Updated for v1.5..." } +# Creates NEW version: attack-pattern--T1234, modified: 2024-02-01T14:00:00Z + +# 3. Add new version to collection as candidate +POST /api/collections/collection--enterprise/candidates +{ + "object_refs": [{ + "id": "attack-pattern--T1234", + "modified": "2024-02-01T14:00:00Z" // Pin to new version + }] +} + +# 4. Review and promote to staged +POST /api/collections/collection--enterprise/candidates/review +{ + "object_refs": [{ + "id": "attack-pattern--T1234", + "modified": "2024-02-01T14:00:00Z" + }], + "from": "work-in-progress", + "to": "reviewed" +} +# → Promoted to staged tier + +# 5. Bump collection to v1.5 +POST /api/collections/collection--enterprise/bump +{ "type": "minor" } +# → Release track now at v1.5 +# → Released tier: attack-pattern--T1234, modified: 2024-02-01T14:00:00Z + +# 6. *** KEY MOMENT: v1.5 is now frozen, but we can keep working! *** + +# 7. Developer immediately starts work on v1.6 changes +POST /api/objects/attack-pattern--T1234 +{ "description": "Updated for v1.6..." } +# Creates ANOTHER new version: attack-pattern--T1234, modified: 2024-02-15T09:00:00Z + +# 8. Add v1.6 version to collection as candidate while v1.5 is still published +POST /api/collections/collection--enterprise/candidates +{ + "object_refs": [{ + "id": "attack-pattern--T1234", + "modified": "2024-02-15T09:00:00Z" // Pin to newest version + }] +} + +# 9. Current collection state: +GET /api/release-tracks/release-track--enterprise?include=all +# Response shows: +# { +# "version": "1.5", +# "members": [{ +# "ref": "attack-pattern--T1234", +# "modified": "2024-02-01T14:00:00Z" // v1.5 version (FROZEN) +# }], +# "candidates": [{ +# "ref": "attack-pattern--T1234", +# "modified": "2024-02-15T09:00:00Z", // v1.6 version (IN DEVELOPMENT) +# "status": "work-in-progress" +# }] +# } + +# Same object, TWO versions tracked simultaneously: +# - members: 2024-02-01 version (frozen for v1.5) +# - candidates: 2024-02-15 version (in development for v1.6) +``` + +### Multi-Collection Independence + +Version pinning also solves cross-collection conflicts: + +```bash +# Collection A: Enterprise ATT&CK +POST /api/collections/collection--enterprise/candidates +{ + "object_refs": [{ + "id": "attack-pattern--T1234", + "modified": "2024-01-15T10:00:00Z" // Pin to specific version + }] +} +# Status in Enterprise collection: "work-in-progress" + +# Collection B: Mobile ATT&CK +POST /api/collections/collection--mobile/candidates +{ + "object_refs": [{ + "id": "attack-pattern--T1234", + "modified": "2024-01-20T14:00:00Z" // Pin to DIFFERENT version + }] +} +# Status in Mobile collection: "reviewed" + +# Collections are completely independent: +# - Enterprise tracks v1 (2024-01-15) as WIP +# - Mobile tracks v2 (2024-01-20) as reviewed +# - No conflict of interest +# - No cross-collection coupling +``` + +### Updating Version Pins + +Collections can update which version they're tracking: + +``` +POST /api/release-tracks/:id/candidates/:objectRef/update-version +``` + +**Request:** +```json +{ + "old_modified": "2024-01-15T10:00:00Z", + "new_modified": "2024-01-20T14:00:00Z" +} +``` + +**Use Cases:** +- Upgrading a candidate to latest version +- Downgrading to previous stable version +- Synchronizing with another collection's version + +## Workflow Scenarios + +### Scenario 1: Standard Release Cycle + +```bash +# 1. Add new techniques as candidates +POST /api/collections/collection--123/candidates +{ "object_refs": ["attack-pattern--new1", "attack-pattern--new2"] } + +# 2. Work on objects (they start as work-in-progress) +# ... development happens ... + +# 3. Transition to awaiting review +POST /api/collections/collection--123/candidates/review +{ + "from": "work-in-progress", + "to": "awaiting-review", + "object_refs": ["attack-pattern--new1"] +} + +# 4. Review and approve +POST /api/collections/collection--123/candidates/review +{ + "from": "awaiting-review", + "to": "reviewed", + "object_refs": ["attack-pattern--new1"] +} +# → auto-promoted to workspace.staged + +# 5. Preview the release +GET /api/release-tracks/collection--123/bump/preview +# → Shows attack-pattern--new1 will be included + +# 6. Bump the collection +POST /api/collections/collection--123/bump +{ "type": "minor" } +# → attack-pattern--new1 moved to x_mitre_contents +# → attack-pattern--new2 remains in candidates (still WIP) +``` + +### Scenario 2: Bulk Review Before Release + +```bash +# Team has been working on 50 techniques +# All are awaiting-review + +# Preview what's ready +GET /api/release-tracks/collection--123/candidates?status=awaiting-review +# → Returns 50 candidates + +# Bulk approve all awaiting review +POST /api/collections/collection--123/candidates/review +{ + "from": "awaiting-review", + "to": "reviewed" +} +# → All 50 auto-promoted to staged + +# Preview release +GET /api/release-tracks/collection--123/bump/preview +# → Shows all 50 will be included + +# Release +POST /api/collections/collection--123/bump +{ "type": "major" } +# → All 50 moved to x_mitre_contents +``` + +### Scenario 3: STIX Freeze Workflow (No Bottleneck) + +```bash +# Realistic timeline demonstrating no freeze bottleneck + +# January 15: Preparing v1.5 release +POST /api/collections/collection--123/candidates +{ + "object_refs": [ + { "id": "attack-pattern--A", "modified": "2024-01-15T10:00:00Z" }, + { "id": "attack-pattern--B", "modified": "2024-01-15T11:00:00Z" } + ] +} + +# January 20: Review complete, promote to staged +POST /api/collections/collection--123/candidates/review +{ + "from": "awaiting-review", + "to": "reviewed" +} + +# January 25: Bump to v1.5 (freeze begins for v1.5 release) +POST /api/collections/collection--123/bump +{ "type": "minor" } +# v1.5 now released with: +# - attack-pattern--A, modified: 2024-01-15T10:00:00Z +# - attack-pattern--B, modified: 2024-01-15T11:00:00Z + +# January 26: *** v1.5 is frozen, but work continues on v1.6 *** + +# Developer starts new changes to Object A +POST /api/objects/attack-pattern--A +{ "description": "Changes for v1.6..." } +# Creates: attack-pattern--A, modified: 2024-01-26T09:00:00Z + +# Add new version as candidate for v1.6 +POST /api/collections/collection--123/candidates +{ + "object_refs": [{ + "id": "attack-pattern--A", + "modified": "2024-01-26T09:00:00Z" // New version + }] +} + +# February 10: More v1.6 work continues +POST /api/objects/attack-pattern--A +{ "description": "More v1.6 updates..." } +# Creates: attack-pattern--A, modified: 2024-02-10T14:00:00Z + +# Update candidate pin to latest version +POST /api/collections/collection--123/candidates/attack-pattern--A/update-version +{ + "old_modified": "2024-01-26T09:00:00Z", + "new_modified": "2024-02-10T14:00:00Z" +} + +# March 1: v1.5 freeze finally ends, v1.6 work is already mostly complete! +# Current state: +# - members (v1.5): attack-pattern--A, modified: 2024-01-15 (still frozen) +# - candidates: attack-pattern--A, modified: 2024-02-10 (already in review) + +# March 5: Bump to v1.6 +POST /api/collections/collection--123/bump +{ "type": "minor" } +# No bottleneck - work continued throughout v1.5 freeze +``` + +### Scenario 4: Development Collection (Permissive Threshold) + +```bash +# Configure collection to include WIP objects +PUT /api/release-tracks/collection--dev/config +{ + "candidacy_threshold": "work-in-progress", + "auto_promote": true +} + +# Add candidates +POST /api/collections/collection--dev/candidates +{ "object_refs": ["attack-pattern--exp1"] } +# → Immediately promoted to staged (meets threshold) + +# Bump immediately +POST /api/collections/collection--dev/bump +{ "type": "minor" } +# → WIP objects included in release +``` + +## Best Practices + +### 1. Use Appropriate Thresholds + +- **Production collections**: `candidacy_threshold: "reviewed"` +- **Team preview collections**: `candidacy_threshold: "awaiting-review"` +- **Development collections**: `candidacy_threshold: "work-in-progress"` + +### 2. Leverage Dry Run + +Always preview releases before bumping: +```bash +GET /api/release-tracks/:id/bump/preview?format=workbench +``` + +### 3. Bulk Operations for Efficiency + +Use bulk transitions for large-scale reviews: +```bash +POST /api/release-tracks/:id/candidates/review +{ + "from": "awaiting-review", + "to": "reviewed" +} +``` + +### 4. Monitor Candidates + +Regularly check candidate status: +```bash +GET /api/release-tracks/:id?include=all +``` + +### 5. Use Events for Automation + +Set up event handlers for: +- Notifications when objects are reviewed +- Auto-staging based on custom rules +- Audit logging for compliance + +--- + +## Virtual Release Track Workflows + +Virtual release tracks follow a different workflow since they don't manage objects directly. Instead, they aggregate content from component tracks. + +See [04_VIRTUAL_TRACKS.md](04_VIRTUAL_TRACKS.md) for complete virtual track documentation. + +### Basic Virtual Track Workflow + +``` +1. Create virtual track (one-time setup) + - Define which component tracks to aggregate + - Configure resolution strategies (latest_tagged, specific_version, etc.) + - Set up snapshot schedule (optional) + +2. Component tracks release independently + - GroupsMonthly releases v1.0, v1.1, v1.2, etc. + - TechniquesQuarterly releases v2.0, v2.1, etc. + - Each on their own cadence + +3. Virtual track snapshot creation (manual or scheduled) + - Resolves latest (or pinned) version from each component + - Creates draft snapshot with resolved composition + - Team receives notification to review + +4. Review and tag + - Team reviews which component versions were included + - Verifies object counts and composition + - Tags snapshot when satisfied +``` + +### Example: Enterprise Virtual Track + +**Setup (one-time):** + +```bash +POST /api/release-tracks/new +{ + "type": "virtual", + "name": "Enterprise ATT&CK", + "composition": { + "component_tracks": [ + { + "track_id": "GroupsMonthly--uuid", + "resolution_strategy": "latest_tagged" + }, + { + "track_id": "TechniquesQuarterly--uuid", + "resolution_strategy": "latest_tagged" + } + ] + }, + "snapshot_schedule": { + "mode": "dates", + "dates": ["2024-01-15T00:00:00Z", "2024-07-15T00:00:00Z"] + } +} +``` + +**Ongoing workflow:** + +``` +Timeline: + +January - June: + - GroupsMonthly releases v1.0, v1.1, v1.2, v1.3, v1.4, v1.5 + - TechniquesQuarterly releases v2.0, v2.1 + +July 15 (scheduled): + - Virtual track snapshot auto-created + - Resolves to: + * GroupsMonthly v1.5 (latest tagged as of July 15) + * TechniquesQuarterly v2.1 (latest tagged as of July 15) + - Draft snapshot created + +July 16 (manual): + - Team reviews draft + - Verifies composition + - Tags as Enterprise v14.0 +``` + +### Key Differences from Standard Tracks + +| Aspect | Standard Track | Virtual Track | +|--------|---------------|---------------| +| Object management | Direct (add candidates, transition status) | Indirect (composed from components) | +| Snapshot creation | When objects/config change | Manual or scheduled only | +| Workflow states | Candidates → Staged → Members | N/A (uses component track states) | +| Version control | Own object versions | Aggregates component versions | +| Primary use case | Source of truth for objects | Publication/release packaging | + +### Virtual Track Best Practices + +1. **Organize standard tracks by object type or domain** + - Example: GroupsMonthly, TechniquesQuarterly, SoftwareBiannual + +2. **Use virtual tracks for publication** + - Standard tracks = internal working tracks + - Virtual tracks = external publication releases + +3. **Schedule snapshots in advance** + - Define release dates up front for predictability + - Use `mode: "dates"` with explicit schedule + +4. **Always review before tagging** + - Scheduled snapshots create drafts + - Manually review composition resolution + - Tag only when satisfied + +5. **Pin component versions conservatively** + - Default to `latest_tagged` to stay current + - Pin only when stability required + +### Virtual Track Constraints + +- Only references **tagged snapshots** from components (never drafts) +- Snapshots created **manually or on schedule** (never event-driven) +- All snapshots start as **drafts** (must explicitly tag) +- Component tracks must have at least one tagged release +- Circular dependencies not allowed + diff --git a/docs/COLLECTIONS_V2/06_ENTITIES.md b/docs/COLLECTIONS_V2/06_ENTITIES.md new file mode 100644 index 00000000..489ab335 --- /dev/null +++ b/docs/COLLECTIONS_V2/06_ENTITIES.md @@ -0,0 +1,395 @@ +## Entities/Schemas/Data Models + +This document tracks new database schemas, interfaces, etc.; as well as changes to any such existing entities. + +### Release Track + +`ReleaseTrack` instances will be tracked as independent MongoDB Collections. The reason for this is because the volume of snapshot permutations is expected to be very high given the frequency of changes that typically occur between releases. + +MongoDB Collections will follow a predictive naming convention that the REST API will (attempt to) detect at runtime: +``` +$name--$uuid +``` +`$name` is set by the user whereas `$uuid` is dynamically generated and must be unique. + +For example, a user might create a new release tracked called `Enterprise`; at the time of instantiation, a unique UUIv4 identifier is generated, `8b0ff8f9-27fd-4d7e-bbc9-8fe9465342af`, resulting in the following MongoDB Collection: +``` +Enterprise--8b0ff8f9-27fd-4d7e-bbc9-8fe9465342af +``` + + +### Release Track Types + +Release tracks can be one of two types: + +1. **Standard Release Tracks**: Traditional release tracks that directly manage objects through the candidate → staged → released workflow +2. **Virtual Release Tracks**: Computed aggregations of other release tracks, used to compose releases from multiple source tracks + +The type is identified by the `stix.type` field: +- Standard tracks: `stix.type` is omitted or set to `"standard"` +- Virtual tracks: `stix.type = "virtual"` + +### Standard Release Track Snapshot Schema + +Each release track snapshot will be tracked as an individual MongoDB Document in its respective `ReleaseTrack` Collection. + +```javascript +{ + // Identity + id: "release-track--123", + type: "standard", // or "virtual" + + // Snapshot metadata + modified: "2024-01-15T16:20:00.000Z", // when the snapshot was created + version: "18.0", // null if draft release + + // Release track metadata + name: "ATT&CK Enterprise", + description: "...", + created: "2024-01-01T10:00:00.000Z", // when the release track was created + created_by_ref: "identity--uuid", + object_marking_refs: ["marking-definition--uuid"], + + // Objects in this snapshot + members: [ + // Objects included in the current/latest release + // These are in the published STIX bundle + { + object_ref: "attack-pattern--aaa", + object_modified: "2024-01-10T10:00:00.000Z" + }, + { + object_ref: "malware--bbb", + object_modified: "2024-01-11T14:30:00.000Z" + }, + { + object_ref: "tool--ccc", + object_modified: "2024-01-12T09:15:00.000Z" + } + ], + + // Staged for next release + staged: [ + // Objects that are reviewed (in THIS release track) and ready for next bump + // Automatically promoted from candidates when track-scoped status → "reviewed" + { + object_ref: "attack-pattern--ddd", + object_modified: "2024-01-14T10:00:00Z", // VERSION PIN: specific object version + object_status: "reviewed", // Track-scoped status + object_staged_at: "2024-01-14T11:00:00Z", + object_staged_by: "reviewer@example.com" + } + ], + + // Work in progress + candidates: [ + // Objects being worked on (in THIS release track), not yet ready for release + { + object_ref: "attack-pattern--eee", + object_modified: "2024-01-12T09:00:00Z", // VERSION PIN: specific object version + object_status: "work-in-progress", // Track-scoped status + object_added_at: "2024-01-10T10:00:00Z", + object_added_by: "alice@example.com" + }, + { + object_ref: "attack-pattern--fff", + object_modified: "2024-01-13T14:00:00Z", // VERSION PIN: specific object version + object_status: "awaiting-review", // Track-scoped status + object_added_at: "2024-01-12T14:30:00Z", + object_added_by: "bob@example.com" + } + ], + + // Configuration + config: { + candidacy_threshold: "awaiting-review", // "work-in-progress" | "awaiting-review" | "reviewed" + auto_promote: true, // Auto-promote reviewed objects to staged + include_candidates_in_snapshots: false, // Whether snapshots include candidates + include_secondary_objects: { + enabled: true, + status_threshold: "reviewed" + } + }, + + // Version history + version_history: [ + { + version: "1.1", + tagged_at: "2024-01-15T17:00:00Z", + tagged_by: "admin@example.com", + snapshot_id: "2024-01-15T16:20:00.000Z", + summary: { + members_count: 3, // Objects in members + promoted_count: 1, // Objects promoted from staged to members + staged_count: 0, // Objects left in staged (if any) + candidate_count: 2 // Objects left in candidates (if any) + } + } + ] +} +``` + +### Version History + +The `version_history` array tracks all tagged releases in reverse chronological order (newest first): + +```javascript +version_history: [ + { + version: "2.0", // Version (MAJOR.MINOR) + tagged_at: "2024-02-01T...", // When the tagging occurred + tagged_by: "user@example.com", // Who performed the tagging + snapshot_id: "2024-02-01T10:00:00.000Z", // Which snapshot was tagged + summary: { + members_count: 3000, + promoted_count: 150 + } + }, + // ... older versions +] +``` + +This provides: +- Complete audit trail of tagged releases +- Attribution for each tagged release +- Chronological release history + +### Object (SDO/SRO/SMO) Document Schema + +Objects maintain a simple reference to which release tracks reference them: + +```javascript +{ + stix: { + id: "attack-pattern--eee", + modified: "2024-01-12T09:00:00Z", // This version's timestamp + type: "attack-pattern", + name: "New Technique", + // ... other STIX properties + }, + workspace: { + // NO global workflow status - status is tracked per-release-track + + // Simple reverse reference for efficient queries + referenced_by: [ + { + release_track_id: "release-track--123", + snapshot_id: "2024-12-15T16:20:00.000Z", + membership_tier: "members", // "members" | "staged" | "candidates" + review_status: "reviewed" // "work-in-progress" | "awaiting-review" | "reviewed" + }, + { + release_track_id: "release-track--456", + snapshot_id: "2025-01-10T11:00:00.000Z", + membership_tier: "candidates", + review_status: "work-in-progress" + } + ], + + // Attribution metadata + workflow_history: [ + { + timestamp: "2024-01-12T09:00:00Z", + modified_by: "alice@example.com", + action: "created" + } + ] + } +} +``` + +**Key Points:** +- **No global `workflow.status`** - status is release-track-specific +- `referenced_by` provides reverse lookup for queries like "show me all release tracks containing this object" +- Same object version can have different statuses in different release tracks +- Multiple versions of same object can exist, each potentially referenced by different release tracks + +### Virtual Release Track Snapshot Schema + +Virtual release tracks compute their contents by aggregating objects from component release tracks. Each virtual track snapshot stores composition rules and resolution metadata. + +```javascript +{ + // Identity + id: "release-track--virtual-uuid", + type: "virtual", // Distinguishes from standard tracks + + // Snapshot metadata + snapshot_id: "2024-03-01T10:00:00.000Z", + modified: "2024-03-01T10:00:00Z", + version: null, // null for draft, or "14.0" for tagged release + + // Release track metadata + name: "Enterprise ATT&CK", + description: "Virtual aggregation of Enterprise content across multiple source tracks", + created: "2024-01-01T10:00:00.000Z", + created_by_ref: "identity--uuid", + object_marking_refs: ["marking-definition--uuid"], + + // Objects in this snapshot (resolved from component tracks) + members: [ + { + object_ref: "intrusion-set--APT1", + object_modified: "2024-02-01T10:00:00Z" + } + // ... 870 total objects + ], + staged: [], + candidates: [], + + // Composition rules - defines how this virtual track is built + composition: { + component_tracks: [ + { + track_id: "release-track--groups-monthly", + resolution_strategy: "latest_tagged", // "latest_tagged" | "specific_version" | "specific_snapshot" + + // Optional: version/snapshot specification for non-latest strategies + version: "5.0", // Used with "specific_version" strategy + snapshot: "2024-02-01T10:00:00Z", // Used with "specific_snapshot" strategy + + // Optional: filters to limit which objects are included + filters: { + object_types: ["intrusion-set"], + domains: ["enterprise"], + stix_pattern: {} // Advanced STIX filtering + } + }, + { + track_id: "release-track--techniques-quarterly", + resolution_strategy: "latest_tagged", + filters: { + object_types: ["attack-pattern"] + } + } + ], + + // Deduplication rules when same object appears in multiple components + deduplication: { + strategy: "prefer_latest_modified", // "prefer_latest_modified" | "prefer_highest_version" | "error" + tier_resolution: "highest_tier", // "highest_tier" | "source_priority" + status_resolution: "highest_status" // "highest_status" | "source_priority" + } + }, + + // Composition resolution - computed at snapshot creation time, immutable + composition_resolution: { + resolved_at: "2024-03-01T10:00:00Z", + + component_snapshots: [ + { + track_id: "release-track--groups-monthly", + track_name: "Groups Monthly", + track_type: "standard", + + // Which snapshot was resolved + resolved_snapshot_id: "2024-02-15T10:00:00.000Z", + resolved_version: "5.2", + + // How it was resolved + strategy_used: "latest_tagged", + filters_applied: { + object_types: ["intrusion-set"] + }, + + // Statistics + total_objects_in_source: 47, + objects_after_filter: 47, + objects_contributed: 47 // After deduplication + }, + { + track_id: "release-track--techniques-quarterly", + track_name: "Techniques Quarterly", + track_type: "standard", + resolved_snapshot_id: "2024-01-15T10:00:00.000Z", + resolved_version: "2.1", + strategy_used: "latest_tagged", + filters_applied: { + object_types: ["attack-pattern"] + }, + total_objects_in_source: 823, + objects_after_filter: 823, + objects_contributed: 823 + } + ], + + // Deduplication report + deduplication: { + total_objects_before: 870, + total_objects_after: 870, + duplicates_found: 0, + conflicts_resolved: [] + }, + + // Native objects (if virtual track has its own objects in addition to composed) + native_objects: { + candidates_count: 0, + staged_count: 0, + members_count: 0 + }, + + // Final statistics + summary: { + total_objects: 870, + by_type: { + "intrusion-set": 47, + "attack-pattern": 823 + }, + by_tier: { + "members": 870, + "staged": 0, + "candidates": 0 + } + } + }, + + // Optional: Virtual tracks can schedule automatic snapshot creation + snapshot_schedule: { + mode: "manual", // "manual" | "cron" | "dates" + cron: "0 0 1 1,7 *", // Cron expression (e.g., Jan 1 and July 1 at midnight) + dates: [ // Or specific dates + "2024-01-01T00:00:00Z", + "2024-07-01T00:00:00Z" + ] + }, + + // Configuration + config: { + candidacy_threshold: "reviewed", + notification_email: "enterprise-team@example.com", + max_composition_depth: 3 // Limit nesting depth for virtual tracks + }, + + // Version history (same as standard tracks) + version_history: [ + { + version: "14.0", + tagged_at: "2024-03-05T14:00:00Z", + tagged_by: "admin@example.com", + snapshot_id: "2024-03-01T10:00:00.000Z", // When snapshot was created + component_versions: { + "GroupsMonthly": "5.2", + "TechniquesQuarterly": "2.1" + } + } + ] +} +``` + +**Key Differences from Standard Tracks:** + +1. **Type Identification**: `stix.type = "virtual"` +2. **Composition Rules**: Defines which component tracks to aggregate and how +3. **Composition Resolution**: Immutable metadata about how snapshot was computed +4. **No Direct Object Management**: Virtual tracks don't add objects as candidates directly (except optional native objects) +5. **Scheduled Snapshots**: Can auto-generate snapshots on schedule +6. **Component Version Tracking**: Version history records which component versions were included + +**Virtual Track Constraints:** + +- Can only reference **tagged snapshots** from component tracks (not drafts) +- Snapshots are created **manually or on schedule** (never event-driven) +- All snapshots start as **drafts** and must be explicitly tagged +- Component tracks must exist and have at least one tagged release +- Circular dependencies are not allowed (VirtualA → VirtualB → VirtualA) +- Maximum composition depth is configurable (default: 3 levels) \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md b/docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md new file mode 100644 index 00000000..71fb2a38 --- /dev/null +++ b/docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md @@ -0,0 +1,138 @@ +## Output Formats + +Release tracks (or rather, each snapshot) can serialize/export to multiple formats via query parameter: + +``` +GET /api/release-tracks/:id?format= +``` + +### Format: `bundle` (Default) + +Standard STIX 2.1 bundle format: + +```json +{ + "type": "bundle", + "id": "bundle--...", + "objects": [ + { + "type": "x-mitre-collection", + "id": "x-mitre-collection--123", + "x_mitre_version": "1.1", + "x_mitre_contents": ["attack-pattern--aaa", "malware--bbb"], + "name": "ATT&CK Enterprise" + }, + { + "type": "attack-pattern", + "id": "attack-pattern--aaa", + "name": "Technique A", + // ... STIX properties only, no workflow info + } + ] +} +``` + +**Characteristics:** +- STIX 2.1 compliant +- Only includes `stix.*` properties +- No workflow states, no workspace data +- Suitable for external publication + +### Format: `filesystemstore` + +STIX FileSystemStore structure (directory tree): + +``` +collection-123/ + x-mitre-collection/ + x-mitre-collection--123.json + attack-pattern/ + attack-pattern--aaa.json + attack-pattern--bbb.json + malware/ + malware--xxx.json +``` + +**Response:** +```json +{ + "format": "filesystemstore", + "structure": { + "x-mitre-collection": [ + { + "filename": "x-mitre-collection--123.json", + "content": { /* STIX object */ } + } + ], + "attack-pattern": [ + { + "filename": "attack-pattern--aaa.json", + "content": { /* STIX object */ } + } + ] + } +} +``` + +> **NOTE**: The `filesystemstore` is still a *concept* that will need additional refinement before it can be implemented. We will need to figure out an optimal way to return JSON files to the user. Optionally, we can attempt to generate an archive and serialize it over the wire, though this may be slow and error prone. Additionally, we can allow users to specify an output path via S3, FTP, etc. + +### Format: `workbench` (Custom) + +Workbench-optimized format with full metadata: + +```json +{ + "collection": { + "id": "x-mitre-collection--123", + "version": "1.1", + "name": "ATT&CK Enterprise", + "modified": "2024-01-15T16:20:00Z" + }, + "objects": [ + { + "stix": { /* Full STIX object */ }, + "workspace": { + "workflow": { + "status": "reviewed", + "reviewed_by": "admin@example.com", + "reviewed_at": "2024-01-14T10:00:00Z" + } + }, + "metadata": { + "collection_tier": "released", // "released" | "staged" | "candidate" + "object_type": "attack-pattern", + "object_name": "Technique A" + } + } + ], + "summary": { + "released_count": 2, + "staged_count": 1, + "candidate_count": 1 + } +} +``` + +**Characteristics:** +- Includes workflow states +- Includes workspace metadata +- Optimized for Workbench UI consumption +- Shows which tier each object belongs to + +> **NOTE**: The response `workbench` object above is just an example. This is not a prescriptive, final draft. The concept is desribed here to illustrate that we can serve information to the frontend in formats more suitable for UI rendering; we are not beholden to exclusively serving content in STIX-compatible formats. + +### Format Usage + +```bash +# Standard STIX bundle for publication +GET /api/release-tracks/:id?format=bundle + +# FileSystemStore export +GET /api/release-tracks/:id?format=filesystemstore + +# Workbench UI with workflow metadata +GET /api/release-tracks/:id?format=workbench + +# Dry run with detailed preview +GET /api/release-tracks/:id/bump/preview?format=workbench +``` \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/99_ERROR_HANDLING.md b/docs/COLLECTIONS_V2/99_ERROR_HANDLING.md new file mode 100644 index 00000000..36358d7e --- /dev/null +++ b/docs/COLLECTIONS_V2/99_ERROR_HANDLING.md @@ -0,0 +1,53 @@ +## Error Handling + +### AlreadyReleasedError + +**Thrown when:** Attempting to bump a snapshot that already has `x_mitre_version` set. + +**HTTP Status:** 409 Conflict + +**Example:** +```json +{ + "error": "This snapshot has already been tagged as version 1.0" +} +``` + +**Solution:** Create a new snapshot by modifying the collection, then bump the new snapshot. + +### InvalidVersionError + +**Thrown when:** +- Explicit version is not valid MAJOR.MINOR format +- Explicit version is not greater than the previous highest version +- Version bump would result in regression + +**HTTP Status:** 400 Bad Request + +**Examples:** +```json +{ + "error": "Version must be greater than current version 1.5" +} +``` + +```json +{ + "error": "Invalid version format. Must match pattern: X.Y (e.g., 1.0, 2.3)" +} +``` + +**Solution:** Provide a valid version that is greater than all previous versions. + +### NotFoundError + +**Thrown when:** Collection with specified ID does not exist. + +**HTTP Status:** 404 Not Found + +**Example:** +```json +{ + "error": "Collection not found" +} +``` \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md b/docs/COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md new file mode 100644 index 00000000..472bb038 --- /dev/null +++ b/docs/COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md @@ -0,0 +1,121 @@ +# Implementation Notes + +## Database Indexes + +```javascript +// Collection candidates lookup +db.collections.createIndex({ 'workspace.candidates.object_ref': 1 }); +db.collections.createIndex({ 'workspace.candidates.status': 1 }); + +// Object collection membership +db.objects.createIndex({ 'workspace.collections.candidates': 1 }); +db.objects.createIndex({ 'workspace.collections.staged': 1 }); +db.objects.createIndex({ 'workspace.workflow.status': 1 }); +``` + +## Validation Rules + +- **Same object version** can only be in one tier per collection (candidates OR staged OR released) +- **Different versions** of same object CAN exist in multiple tiers simultaneously +- Status transitions must be valid: WIP → Awaiting → Reviewed (no backwards transitions) +- Candidacy threshold must be valid enum value +- Object version must exist before adding as candidate (validate `stix.id` and `stix.modified` exist) +- Version pin (`object_modified`) is immutable once set for a tier entry + +## Performance Considerations + +- Bulk operations should use batch updates +- Event handlers should be async and non-blocking +- Large collections (>10k objects) may need pagination +- Consider caching for `bump/preview` on large collections + +## Integrating with the Event-Driven Architecture + +### Events Published + +```javascript +// When object status changes within a collection (collection-scoped) +eventBus.emit('release-track:status-changed', { + collectionId: 'x-mitre-collection--123', + objectId: 'attack-pattern--eee', + objectModified: '2024-01-12T09:00:00Z', // Version pin + oldStatus: 'work-in-progress', + newStatus: 'awaiting-review', + changedBy: 'user@example.com', + changedAt: '2024-01-15T10:00:00Z' +}); + +// When object version is added to collection candidates +eventBus.emit('release-track:candidate-added', { + collectionId: 'x-mitre-collection--123', + objectId: 'attack-pattern--eee', + objectModified: '2024-01-12T09:00:00Z', // Version pin + status: 'work-in-progress', + addedBy: 'user@example.com' +}); + +// When object is promoted to staged +eventBus.emit('release-track:object-staged', { + collectionId: 'x-mitre-collection--123', + objectId: 'attack-pattern--ddd', + objectModified: '2024-01-14T10:00:00Z', // Version pin + status: 'reviewed', + promotedBy: 'auto' // or user email +}); + +// When collection is bumped +eventBus.emit('release-track:released', { + collectionId: 'x-mitre-collection--123', + version: '1.2', + promotedCount: 1, + promotedObjects: [ + { + objectId: 'attack-pattern--ddd', + objectModified: '2024-01-14T10:00:00Z' // Version included in release + } + ], + releasedBy: 'admin@example.com' +}); +``` + +### Event Handlers + +```javascript +// Auto-promote on status change (collection-scoped) +eventBus.on('release-track:status-changed', async (event) => { + if (event.newStatus === 'reviewed') { + const collection = await Collection.findById(event.collectionId); + + if (collection.workspace.config.auto_promote) { + // Move this specific version from candidates to staged + await promoteToStaged( + collection, + event.objectId, + event.objectModified // Preserve version pin + ); + } + } +}); + +// Update object's referenced_by tracking +eventBus.on('release-track:object-staged', async (event) => { + // Update the specific object version + await updateObject( + { 'stix.id': event.objectId, 'stix.modified': event.objectModified }, + { + $set: { + 'workspace.referenced_by.$[elem].tier': 'staged', + 'workspace.referenced_by.$[elem].status': event.status + } + }, + { + arrayFilters: [ + { + 'elem.collection_id': event.collectionId, + 'elem.tier': 'candidates' + } + ] + } + ); +}); +``` diff --git a/docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md b/docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md new file mode 100644 index 00000000..8dec6910 --- /dev/null +++ b/docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md @@ -0,0 +1,107 @@ +## Workflow Examples + +### Example 1: Standard Release Cycle + +```bash +# 1. Create initial collection +POST /api/release-tracks/new +{ "name": "My Release", ... } +# Creates: snapshot 1, x_mitre_version: null + +# 2. Update contents +POST /api/release-tracks/release--123/contents +{ "x_mitre_contents": [...] } +# Creates: snapshot 2, x_mitre_version: null + +# 3. Update metadata +POST /api/release-tracks/release--123/meta +{ "description": "Updated description" } +# Creates: snapshot 3, x_mitre_version: null + +# 4. Ready for first release - tag as v1.0 +POST /api/release-tracks/release--123/bump +{ "type": "major" } +# Updates: snapshot 3, x_mitre_version: "1.0" (IN-PLACE) + +# 5. Continue development +POST /api/release-tracks/release--123/contents +{ "x_mitre_contents": [...] } +# Creates: snapshot 4, x_mitre_version: null + +# 6. Minor release +POST /api/release-tracks/release--123/bump +{ "type": "minor" } +# Updates: snapshot 4, x_mitre_version: "1.1" (IN-PLACE) + +# 7. More changes +POST /api/release-tracks/release--123/contents +{ "x_mitre_contents": [...] } +# Creates: snapshot 5, x_mitre_version: null + +# 8. Another minor release +POST /api/release-tracks/release--123/bump +{ "type": "minor" } +# Updates: snapshot 5, x_mitre_version: "1.2" (IN-PLACE) +``` + +**Resulting Timeline:** +``` +snapshot 1: modified: T1, x_mitre_version: null +snapshot 2: modified: T2, x_mitre_version: null +snapshot 3: modified: T3, x_mitre_version: "1.0" ← RELEASE +snapshot 4: modified: T4, x_mitre_version: "1.1" ← RELEASE +snapshot 5: modified: T5, x_mitre_version: "1.2" ← RELEASE +``` + +### Example 2: Selective Release Tagging + +```bash +# Create several snapshots +POST /api/collections/collection--456/contents # snapshot 1 +POST /api/collections/collection--456/contents # snapshot 2 +POST /api/collections/collection--456/contents # snapshot 3 +POST /api/collections/collection--456/contents # snapshot 4 +POST /api/collections/collection--456/contents # snapshot 5 + +# Only tag snapshots 2 and 5 as releases +POST /api/collections/collection--456/modified//bump +{ "version": "1.0" } + +POST /api/collections/collection--456/bump # Latest = snapshot 5 +{ "version": "1.1" } +``` + +**Resulting Timeline:** +``` +snapshot 1: x_mitre_version: null (skipped) +snapshot 2: x_mitre_version: "1.0" ← RELEASE +snapshot 3: x_mitre_version: null (skipped) +snapshot 4: x_mitre_version: null (skipped) +snapshot 5: x_mitre_version: "1.1" ← RELEASE +``` + +This mirrors Git's ability to tag any commit, not just the latest. + +### Example 3: Handling Already-Released Snapshots + +```bash +# Tag latest snapshot +POST /api/collections/collection--789/bump +{ "version": "1.0" } +# Success: snapshot tagged as v1.0 + +# Attempt to bump the same snapshot again +POST /api/collections/collection--789/bump +{ "version": "1.1" } +# Error: AlreadyReleasedError - "This snapshot has already been tagged as version 1.0" + +# Solution: Make a change first (creates new snapshot) +POST /api/collections/collection--789/contents +{ "x_mitre_contents": [...] } +# Creates new snapshot + +# Now bump the new snapshot +POST /api/collections/collection--789/bump +{ "version": "1.1" } +# Success: new snapshot tagged as v1.1 +``` From 3f5f1a644fa8c493fb3eb0154330d3417ec66f65 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:46:12 -0500 Subject: [PATCH 176/370] docs(feature-planning): modify virtual tracks structure and release workfow --- docs/COLLECTIONS_V2/02_TERMINOLOGY.md | 66 +- docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md | 729 +++++++++++++-------- docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md | 173 ++++- docs/COLLECTIONS_V2/06_ENTITIES.md | 80 ++- 4 files changed, 733 insertions(+), 315 deletions(-) diff --git a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md b/docs/COLLECTIONS_V2/02_TERMINOLOGY.md index 44bdc850..14e2721b 100644 --- a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md +++ b/docs/COLLECTIONS_V2/02_TERMINOLOGY.md @@ -158,9 +158,13 @@ The **tagging operation** marks an existing snapshot as a tagged release by assi ### Object Membership Tiers -Every snapshot (draft or tagged) contains objects which can be classified into any of three categories: +The tier system differs between **standard release tracks** and **virtual release tracks**. -#### 1. Candidate Objects +#### Standard Release Tracks: Three-Tier System + +Standard release tracks use three tiers to manage the object lifecycle from development to release: + +##### 1. Candidate Objects **Location:** `workspace.candidates` @@ -173,12 +177,17 @@ Every snapshot (draft or tagged) contains objects which can be classified into a - Objects in this tier are NOT included in published STIX bundles by default - Automatically promoted to staged tier when status reaches the candidacy threshold +**Duplicate Rules:** +- Cannot contain exact duplicates (same `object_ref` + `object_modified` pair) +- **CAN** contain multiple versions of the same object (same `object_ref`, different `object_modified` timestamps) +- Example: Can have `attack-pattern--T1234, modified: 2024-01-15` AND `attack-pattern--T1234, modified: 2024-02-20` simultaneously + **Examples:** - "Add these 10 techniques as candidate objects" - "There are 47 candidate objects in work-in-progress status" - "Transition candidate objects from awaiting-review to reviewed" -#### 2. Staged Objects +##### 2. Staged Objects **Location:** `workspace.staged` @@ -191,12 +200,17 @@ Every snapshot (draft or tagged) contains objects which can be classified into a - Moved to member objects tier (`x_mitre_contents`) when the snapshot is tagged - NOT included in published STIX bundles until the snapshot is tagged +**Duplicate Rules:** +- Cannot contain exact duplicates (same `object_ref` + `object_modified` pair) +- **CANNOT** contain multiple versions of the same object +- If a promotion would create a duplicate (different version of same object already in staged), conflict resolution policy applies + **Examples:** - "There are 12 staged objects ready for the next release" - "Promote all reviewed candidates to staged" - "Preview which staged objects will be included in the next tagged release" -#### 3. Member Objects +##### 3. Member Objects **Location:** `stix.x_mitre_contents` @@ -209,11 +223,55 @@ Every snapshot (draft or tagged) contains objects which can be classified into a - Represents the production-ready, published content - Only updated when a snapshot is tagged (staged objects are promoted to members) +**Duplicate Rules:** +- Cannot contain exact duplicates (same `object_ref` + `object_modified` pair) +- **CANNOT** contain multiple versions of the same object +- If a promotion would create a duplicate (different version of same object already in members), conflict resolution policy applies + **Examples:** - "The Enterprise release track has 3,247 member objects" - "Export all member objects as a STIX bundle" - "Which version of Technique T1234 is in the member objects?" +#### Virtual Release Tracks: Two-Tier System + +Virtual release tracks use a simplified two-tier system since they aggregate already-released content: + +##### 1. Member Objects + +**Location:** `stix.x_mitre_contents` + +**Definition:** Successfully synced objects from component tracks. + +**Characteristics:** +- Contains objects synced from component tracks' `members` tiers +- Objects that were automatically resolved using the deduplication strategy +- OR objects manually promoted from quarantine +- These objects are included in published STIX bundles +- No workflow states (no work-in-progress, awaiting-review, reviewed) + +**Duplicate Rules:** +- Cannot contain exact duplicates (same `object_ref` + `object_modified` pair) +- **CANNOT** contain multiple versions of the same object +- Deduplication strategy determines which version to keep when conflicts occur + +##### 2. Quarantine + +**Location:** `workspace.quarantine` + +**Definition:** Conflicting objects that require manual resolution. + +**Characteristics:** +- Only populated when using `quarantine` deduplication strategy +- Contains objects that couldn't be automatically resolved due to conflicts +- NOT included in published STIX bundles +- Requires manual intervention to promote one version to members + +**Duplicate Rules:** +- Cannot contain exact duplicates (same `object_ref` + `object_modified` pair) +- **CAN** contain multiple versions of the same object (different versions from different component tracks) +- Example: Can have `attack-pattern--T1234, modified: 2024-01-15` (from Track A) AND `attack-pattern--T1234, modified: 2024-02-20` (from Track B) simultaneously + --- ### Virtual Release Track diff --git a/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md b/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md index 9354332f..e00bb67f 100644 --- a/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md +++ b/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md @@ -59,61 +59,67 @@ Virtual tracks are identified by `stix.type = "virtual"` in their schema. ```javascript { - stix: { - id: "x-mitre-collection--virtual-uuid", - type: "virtual", // Distinguishes from standard tracks - modified: "2024-03-01T10:00:00Z", - x_mitre_version: null, // Draft snapshot (or "14.0" if tagged) - name: "Enterprise ATT&CK", - description: "Virtual aggregation of Enterprise content" - }, - - workspace: { - // Composition rules (how to build this virtual track) - composition: { - component_tracks: [ - { - track_id: "GroupsMonthly--uuid", - resolution_strategy: "latest_tagged", - filters: { - object_types: ["intrusion-set"], - // Additional filters... - } - }, - { - track_id: "TechniquesQuarterly--uuid", - resolution_strategy: "latest_tagged", - filters: { - object_types: ["attack-pattern"] - } + // Identity + id: "release-track--uuid-virtual", + type: "virtual", // Distinguishes from standard tracks + + // Snapshot metadata + snapshot_id: "2024-03-01T10:00:00.000Z", + modified: "2024-03-01T10:00:00Z", + version: null, // Draft snapshot (or "14.0" if tagged) + + // Release track metadata + name: "Enterprise ATT&CK", + description: "Virtual aggregation of Enterprise content", + created: "2024-01-01T10:00:00.000Z", + created_by_ref: "identity--uuid", + object_marking_refs: ["marking-definition--uuid"], + + // Objects in this snapshot (Virtual tracks use a 2-tier system) + members: [], // Successfully synced objects from component tracks + quarantine: [], // Conflicting objects that require manual resolution + + // Composition rules (how to build this virtual track) + composition: { + component_tracks: [ + { + track_id: "release-track--uuid-1", + resolution_strategy: "latest_tagged", + priority: 1, // Used with prioritize_higher_priority strategy (lower number = higher priority) + filters: { + object_types: ["intrusion-set"], + // Additional filters... + } + }, + { + track_id: "release-track--uuid-2", + resolution_strategy: "latest_tagged", + priority: 2, + filters: { + object_types: ["attack-pattern"] } - ], - - deduplication: { - strategy: "prefer_latest_modified", - tier_resolution: "highest_tier", - status_resolution: "highest_status" } - }, + ], - // Snapshot schedule configuration - snapshot_schedule: { - mode: "manual" | "cron" | "dates", - cron: "0 0 1 1,7 *", // Jan 1 and July 1 at midnight - dates: ["2024-01-01T00:00:00Z", "2024-07-01T00:00:00Z"] - }, + deduplication: { + strategy: "prioritize_latest_object" // See Deduplication Strategies below + } + }, - // Optional: Virtual track can also have its own native objects - candidates: [], // Virtual track's own objects (not from components) - staged: [], + // Snapshot schedule configuration + snapshot_schedule: { + mode: "manual", // "manual" | "cron" | "dates" + cron: "0 0 1 1,7 *", // Jan 1 and July 1 at midnight + dates: ["2024-01-01T00:00:00Z", "2024-07-01T00:00:00Z"] + }, - config: { - candidacy_threshold: "reviewed", - auto_promote: true - }, + // Configuration + config: { + candidacy_threshold: "reviewed", + auto_promote: true + }, - version_history: [] - } + version_history: [] } ``` @@ -127,13 +133,13 @@ Always resolves to the most recent **tagged snapshot** from the component track. ```javascript { - track_id: "GroupsMonthly--uuid", + track_id: "release-track--uuid-1", resolution_strategy: "latest_tagged" } // At virtual snapshot time (e.g., March 1, 2024): -// 1. Query GroupsMonthly for all snapshots where x_mitre_version !== null -// 2. Sort by stix.modified DESC +// 1. Query GroupsMonthly for all snapshots where version !== null +// 2. Sort by modified DESC // 3. Take first result // → Resolves to GroupsMonthly v5.2 (released Feb 15, 2024) ``` @@ -146,13 +152,13 @@ Resolves to a specific semantic version from the component track. ```javascript { - track_id: "GroupsMonthly--uuid", + track_id: "release-track--uuid-1", resolution_strategy: "specific_version", version: "5.0" } // At virtual snapshot time: -// 1. Query GroupsMonthly for snapshot where x_mitre_version === "5.0" +// 1. Query GroupsMonthly for snapshot where version === "5.0" // → Resolves to GroupsMonthly v5.0 (regardless of when snapshot occurs) ``` @@ -160,22 +166,33 @@ Resolves to a specific semantic version from the component track. #### 3. `specific_snapshot` -Resolves to a specific snapshot by its `stix.modified` timestamp. +Resolves to a specific snapshot by its `modified` timestamp. ```javascript { - track_id: "GroupsMonthly--uuid", + track_id: "release-track--uuid-1", resolution_strategy: "specific_snapshot", snapshot: "2024-02-01T10:00:00Z" } // At virtual snapshot time: -// 1. Query GroupsMonthly for snapshot where stix.modified === "2024-02-01T10:00:00Z" +// 1. Query GroupsMonthly for snapshot where modified === "2024-02-01T10:00:00Z" // → Resolves to that specific snapshot ``` **Use case:** "Lock to exact snapshot for reproducibility" +### Component Track Sync Rules + +Virtual tracks **only sync from component tracks' `members` tier** (`x_mitre_contents`). This ensures that virtual tracks only aggregate objects that have been officially released in their source tracks. + +**Important:** +- Virtual tracks reference **tagged snapshots only** (never drafts) +- Virtual tracks pull objects from **`members` tier only** (never staged or candidates) +- This guarantees that virtual track releases are composed of stable, released content + +**Rationale:** Since virtual tracks can only reference tagged snapshots from component tracks, it makes sense to only pull from the `members` tier, which contains the released objects from those snapshots. + ### Filters Each component track can specify filters to limit which objects are included: @@ -195,44 +212,185 @@ filters: { } ``` -### Deduplication Rules +### Deduplication Strategies + +When multiple component tracks contain the same object (same `stix.id`), a conflict occurs during the sync operation. The virtual track's deduplication strategy determines how to resolve the conflict. Four strategies are available: + +#### 1. `prioritize_latest_object` + +Keep the version with the newest `modified` timestamp, regardless of which component track it came from. + +```javascript +deduplication: { + strategy: "prioritize_latest_object" +} +``` + +**Example:** +```javascript +// GroupsMonthly v5.2 has: +// intrusion-set--APT1, modified: 2024-02-01T10:00:00Z + +// MobileGroups v3.1 has: +// intrusion-set--APT1, modified: 2024-01-15T14:00:00Z + +// Virtual track sync result: +// → Uses 2024-02-01 version from GroupsMonthly (newer object) +// → Added to virtual track's members +``` + +**Use case:** "Always use the most recently updated object, regardless of source" + +#### 2. `prioritize_latest_snapshot` -When multiple component tracks contain the same object: +Keep the version from the component track whose resolved snapshot has the newest `modified` timestamp. This can result in syncing **older** versions of objects if they came from a more recently released snapshot. ```javascript deduplication: { - // Which version of the object to include - strategy: "prefer_latest_modified" | "prefer_highest_version" | "error", + strategy: "prioritize_latest_snapshot" +} +``` - // If object is in different tiers across components, which tier wins? - tier_resolution: "highest_tier" | "source_priority", +**Example:** +```javascript +// GroupsMonthly v5.2 +// - Snapshot created: 2024-02-15T10:00:00Z +// - intrusion-set--APT1, modified: 2024-02-01T10:00:00Z + +// MobileGroups v3.1 +// - Snapshot created: 2024-01-10T10:00:00Z +// - intrusion-set--APT1, modified: 2024-02-05T10:00:00Z + +// Virtual track sync result: +// → Uses 2024-02-01 version from GroupsMonthly +// → GroupsMonthly snapshot is newer (2024-02-15), even though APT1 object is older +// → Added to virtual track's members +``` + +**Use case:** "Trust the more recently released track, even if individual objects are older" + +#### 3. `prioritize_higher_priority` - // If object has different statuses across components, which status wins? - status_resolution: "highest_status" | "source_priority" +Keep the version from the component track with the higher priority (lower priority number). Each component track must have a unique priority value. + +```javascript +composition: { + component_tracks: [ + { + track_id: "release-track--authoritative", + resolution_strategy: "latest_tagged", + priority: 1, // Higher priority (lower number = higher priority) + filters: { object_types: ["intrusion-set"] } + }, + { + track_id: "release-track--supplemental", + resolution_strategy: "latest_tagged", + priority: 2, // Lower priority + filters: { object_types: ["intrusion-set"] } + } + ], + deduplication: { + strategy: "prioritize_higher_priority" + } } ``` **Example:** +```javascript +// Authoritative track (priority: 1) has: +// intrusion-set--APT1, modified: 2024-01-01T10:00:00Z + +// Supplemental track (priority: 2) has: +// intrusion-set--APT1, modified: 2024-02-15T10:00:00Z + +// Virtual track sync result: +// → Uses 2024-01-01 version from Authoritative track +// → Priority 1 wins, even though object is older +// → Added to virtual track's members +``` + +**Use case:** "One track is authoritative; always prefer its version over others" + +**Important:** Component tracks cannot have duplicate priority values. The API will reject composition configurations with conflicting priorities. + +#### 4. `quarantine` + +Don't automatically choose a version. Instead, store **both** versions in the virtual track's `quarantine` tier for manual review and resolution. ```javascript -// GroupsMonthly has: intrusion-set--APT1 -// - modified: 2024-02-01 -// - tier: members (released) -// - status: reviewed +deduplication: { + strategy: "quarantine" +} +``` -// MobileGroups has: intrusion-set--APT1 -// - modified: 2024-01-15 -// - tier: candidates -// - status: work-in-progress +**Example:** +```javascript +// GroupsMonthly has: intrusion-set--APT1, modified: 2024-02-01 +// MobileGroups has: intrusion-set--APT1, modified: 2024-01-15 -// Virtual track with deduplication: +// Virtual track sync result: { - strategy: "prefer_latest_modified", // → Use 2024-02-01 from GroupsMonthly - tier_resolution: "highest_tier", // → Use "members" tier - status_resolution: "highest_status" // → Use "reviewed" status + members: [ + // APT1 is NOT included here + // ... other non-conflicting objects + ], + quarantine: [ + { + object_ref: "intrusion-set--APT1", + object_modified: "2024-02-01T10:00:00Z", + source_track_id: "release-track--groups-monthly", + source_track_name: "Groups Monthly", + source_snapshot_version: "5.2", + conflict_reason: "duplicate_object" + }, + { + object_ref: "intrusion-set--APT1", + object_modified: "2024-01-15T14:00:00Z", + source_track_id: "release-track--mobile-groups", + source_track_name: "Mobile Groups", + source_snapshot_version: "3.1", + conflict_reason: "duplicate_object" + } + ] } ``` +**Use case:** "Conflicts require human review; don't automatically choose a version" + +**Follow-up workflow:** Users review the quarantined objects and manually promote one version to `members` during a future snapshot update. The quarantined objects remain in the virtual track until manual intervention occurs. + +### Virtual Track Two-Tier System + +Unlike standard release tracks (which use a three-tier system: candidates → staged → members), virtual tracks use a simplified **two-tier system**: + +1. **`members`** - Successfully synced objects from component tracks + - Contains objects that were either: + - Synced from component tracks without conflicts, OR + - Manually promoted from quarantine after conflict resolution + - These objects are included in published STIX bundles + - No duplicate objects allowed (unique by `stix.id`) + +2. **`quarantine`** - Conflicting objects requiring manual resolution + - Contains objects that couldn't be automatically resolved due to conflicts + - Only populated when using `quarantine` deduplication strategy + - Can contain multiple versions of the same object (different `modified` timestamps) + - NOT included in published STIX bundles + - Requires manual intervention to resolve + +**Comparison to Standard Tracks:** + +| Feature | Standard Track | Virtual Track | +|---------|---------------|---------------| +| Tiers | candidates, staged, members | quarantine, members | +| Object management | Direct (add/remove objects) | Indirect (synced from components) | +| Workflow states | work-in-progress, awaiting-review, reviewed | N/A | +| Auto-promotion | Based on candidacy threshold | N/A | +| Manual promotion | candidates → staged → members | quarantine → members | + +**Why only two tiers?** + +Virtual tracks aggregate content from component tracks that have already gone through the full workflow (candidates → staged → members). Virtual tracks don't need the intermediate `staged` tier because they're composing already-released content. The only workflow step is resolving conflicts via the `quarantine` tier. + ## Virtual Track Snapshot Lifecycle ### 1. Snapshot Creation @@ -255,35 +413,49 @@ POST /api/release-tracks/:id/snapshots/create **Response:** ```json { - "stix": { - "id": "x-mitre-collection--virtual-uuid", - "modified": "2024-03-01T10:00:00Z", - "x_mitre_version": null, // Draft snapshot - "type": "virtual" - }, + "id": "release-track--uuid-virtual", + "type": "virtual", + "snapshot_id": "2024-03-01T10:00:00.000Z", + "modified": "2024-03-01T10:00:00Z", + "version": null, + "name": "Enterprise ATT&CK", + "description": "Virtual aggregation of Enterprise content", "composition_resolution": { "resolved_at": "2024-03-01T10:00:00Z", "component_snapshots": [ { - "track_id": "GroupsMonthly--uuid", + "track_id": "release-track--uuid-1", "track_name": "Groups Monthly", - "resolved_snapshot": "2024-02-15T10:00:00Z", + "track_type": "standard", + "resolved_snapshot_id": "2024-02-15T10:00:00.000Z", "resolved_version": "5.2", "strategy_used": "latest_tagged", - "object_count": 47 + "total_objects_in_source": 47, + "objects_after_filter": 47, + "objects_contributed": 47 }, { - "track_id": "TechniquesQuarterly--uuid", + "track_id": "release-track--uuid-2", "track_name": "Techniques Quarterly", - "resolved_snapshot": "2024-01-15T10:00:00Z", + "track_type": "standard", + "resolved_snapshot_id": "2024-01-15T10:00:00.000Z", "resolved_version": "2.1", "strategy_used": "latest_tagged", - "object_count": 823 + "total_objects_in_source": 823, + "objects_after_filter": 823, + "objects_contributed": 823 } ], - "total_objects": 870, - "duplicates_resolved": 0 + "deduplication": { + "total_objects_before": 870, + "total_objects_after": 870, + "duplicates_found": 0, + "conflicts_resolved": [] + }, + "summary": { + "total_objects": 870 + } } } ``` @@ -291,13 +463,19 @@ POST /api/release-tracks/:id/snapshots/create **Business Logic:** 1. For each component track in `composition.component_tracks`: - Resolve snapshot based on `resolution_strategy` - - **Validate that resolved snapshot is tagged** (x_mitre_version !== null) + - **Validate that resolved snapshot is tagged** (version !== null) + - Pull objects from component track's **`members` tier only** (`x_mitre_contents`) - Apply `filters` to get subset of objects - - Collect all object references -2. Apply deduplication rules across all components + - Collect all object references with source metadata +2. Apply deduplication rules across all components: + - If no conflicts: objects go to virtual track's `members` + - If conflicts + `quarantine` strategy: both versions go to `quarantine` + - If conflicts + other strategies: winning version goes to `members` 3. Create new virtual track snapshot with: - - New `stix.modified` timestamp - - `x_mitre_version = null` (always starts as draft) + - New `snapshot_id` and `modified` timestamp + - `version = null` (always starts as draft) + - `members` array (successfully synced objects) + - `quarantine` array (conflicting objects, if any) - `composition_resolution` metadata (what was included) 4. Return snapshot with resolution details @@ -316,7 +494,7 @@ snapshot_schedule: { ```javascript scheduler.register({ type: "virtual-track-snapshot", - trackId: "EnterpriseTwiceAnnual--uuid", + trackId: "release-track--uuid-virtual", schedule: "0 0 1 1,7 *", handler: async (trackId) => { await virtualTrackService.createSnapshot(trackId, { @@ -364,15 +542,14 @@ POST /api/release-tracks/:id/snapshots/:modified/bump **Response:** ```json { - "stix": { - "id": "x-mitre-collection--virtual-uuid", - "modified": "2024-03-01T10:00:00Z", // Unchanged - "x_mitre_version": "14.0", // Now tagged - "type": "virtual" - }, + "id": "release-track--uuid-virtual", + "type": "virtual", + "snapshot_id": "2024-03-01T10:00:00.000Z", + "modified": "2024-03-01T10:00:00Z", + "version": "14.0", + "name": "Enterprise ATT&CK", "composition_resolution": { - // Preserved from snapshot creation "resolved_at": "2024-03-01T10:00:00Z", "component_snapshots": [...] }, @@ -380,12 +557,12 @@ POST /api/release-tracks/:id/snapshots/:modified/bump "version_history": [ { "version": "14.0", - "tagged_at": "2024-03-05T14:00:00Z", // Later than snapshot creation + "tagged_at": "2024-03-05T14:00:00Z", "tagged_by": "admin@example.com", - "snapshot_created_at": "2024-03-01T10:00:00Z", + "snapshot_id": "2024-03-01T10:00:00.000Z", "component_versions": { - "GroupsMonthly": "5.2", - "TechniquesQuarterly": "2.1" + "Groups Monthly": "5.2", + "Techniques Quarterly": "2.1" } } ] @@ -393,10 +570,10 @@ POST /api/release-tracks/:id/snapshots/:modified/bump ``` **Business Logic:** -1. Validate snapshot exists and is a draft (x_mitre_version === null) +1. Validate snapshot exists and is a draft (version === null) 2. Calculate/validate version number -3. Set x_mitre_version on snapshot (in-place update) -4. Add entry to workspace.version_history +3. Set version on snapshot (in-place update) +4. Add entry to version_history 5. Snapshot is now immutable ### 4. Snapshot Export @@ -420,9 +597,9 @@ GET /api/release-tracks/:id/snapshots/:modified?format=bundle "x_mitre_version": "14.0", "name": "Enterprise ATT&CK", "x_mitre_contents": [ - "intrusion-set--APT1", - "intrusion-set--APT2", - "attack-pattern--T1234", + { "object_ref": "intrusion-set--APT1", "object_modified": "2024-02-01T10:00:00Z" }, + { "object_ref": "intrusion-set--APT2", "object_modified": "2024-01-15T10:00:00Z" }, + { "object_ref": "attack-pattern--T1234", "object_modified": "2024-01-10T10:00:00Z" } // ... all 870 objects ] }, @@ -441,65 +618,66 @@ Each virtual track snapshot stores metadata about how it was composed: ```javascript { - stix: { - modified: "2024-03-01T10:00:00Z", - x_mitre_version: "14.0" - }, + // Identity and snapshot metadata + id: "release-track--uuid-virtual", + type: "virtual", + snapshot_id: "2024-03-01T10:00:00.000Z", + modified: "2024-03-01T10:00:00Z", + version: "14.0", + + // Composition resolution metadata + composition_resolution: { + resolved_at: "2024-03-01T10:00:00Z", + + component_snapshots: [ + { + track_id: "release-track--uuid-1", + track_name: "Groups Monthly", + track_type: "standard", + + // Which snapshot was used + resolved_snapshot_id: "2024-02-15T10:00:00.000Z", + resolved_version: "5.2", + + // How it was resolved + strategy_used: "latest_tagged", + filters_applied: { + object_types: ["intrusion-set"] + }, - workspace: { - composition_resolution: { - resolved_at: "2024-03-01T10:00:00Z", - - component_snapshots: [ - { - track_id: "GroupsMonthly--uuid", - track_name: "Groups Monthly", - track_type: "standard", - - // Which snapshot was used - resolved_snapshot: "2024-02-15T10:00:00Z", - resolved_version: "5.2", - - // How it was resolved - strategy_used: "latest_tagged", - filters_applied: { - object_types: ["intrusion-set"] - }, - - // Statistics - total_objects_in_source: 47, - objects_after_filter: 47, - objects_contributed: 47 // After deduplication - } - ], - - // Deduplication report - deduplication: { - total_objects_before: 870, - total_objects_after: 870, - duplicates_found: 0, - conflicts_resolved: [] - }, + // Statistics + total_objects_in_source: 47, + objects_after_filter: 47, + objects_contributed: 47 // After deduplication + } + ], - // Native objects (if any) - native_objects: { - candidates_count: 0, - staged_count: 0, - members_count: 0 - }, + // Deduplication report + deduplication: { + total_objects_before: 870, + total_objects_after: 870, + duplicates_found: 0, + conflicts_resolved: [] + }, - // Final statistics - summary: { - total_objects: 870, - by_type: { - "intrusion-set": 47, - "attack-pattern": 823 - }, - by_tier: { - "members": 870, - "staged": 0, - "candidates": 0 - } + // Native objects (if any) + native_objects: { + candidates_count: 0, + staged_count: 0, + members_count: 0 + }, + + // Final statistics + summary: { + total_objects: 870, + by_type: { + "intrusion-set": 47, + "attack-pattern": 823 + }, + by_tier: { + "members": 870, + "staged": 0, + "candidates": 0 } } } @@ -515,7 +693,7 @@ Each virtual track snapshot stores metadata about how it was composed: for (const component of composition.component_tracks) { const snapshot = await resolveSnapshot(component); - if (snapshot.stix.x_mitre_version === null) { + if (snapshot.version === null) { throw new ValidationError( `Component track ${component.track_id} resolved to draft snapshot. ` + `Virtual tracks can only reference tagged snapshots.` @@ -526,66 +704,37 @@ for (const component of composition.component_tracks) { **User experience:** ```bash -POST /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/create +POST /api/release-tracks/release-track--uuid-virtual/snapshots/create # Error response: { "error": "ValidationError", "message": "Cannot create virtual snapshot: component track 'GroupsMonthly' has no tagged releases", "details": { - "component": "GroupsMonthly--uuid", + "component": "release-track--uuid-1", "issue": "No tagged snapshots found (all snapshots are drafts)" } } ``` -#### 2. Circular dependency prevention +#### 2. Component tracks must be standard tracks ```javascript // When creating/updating virtual track composition -async function validateNoCycles(virtualTrack) { - const visited = new Set(); - const stack = [virtualTrack.stix.id]; - - while (stack.length > 0) { - const currentId = stack.pop(); - - if (visited.has(currentId)) { - throw new ValidationError(`Circular dependency detected: ${currentId}`); - } - - visited.add(currentId); - - const track = await getReleaseTrack(currentId); - - if (track.stix.type === "virtual") { - for (const component of track.workspace.composition.component_tracks) { - stack.push(component.track_id); - } +async function validateComponentsAreStandard(virtualTrack) { + for (const component of virtualTrack.composition.component_tracks) { + const track = await getReleaseTrack(component.track_id); + + if (track.type === "virtual") { + throw new ValidationError( + `Virtual tracks can only compose from standard tracks. ` + + `Component track ${component.track_id} is a virtual track.` + ); } } } ``` -#### 3. Maximum composition depth - -```javascript -workspace: { - config: { - max_composition_depth: 3 // Limit nesting to prevent performance issues - } -} -``` - -Virtual track nesting example: -``` -VirtualA (depth 0) - → VirtualB (depth 1) - → VirtualC (depth 2) - → StandardD (depth 3) ✓ allowed - → StandardE (depth 4) ✗ exceeds max_composition_depth -``` - ## API Reference ### Create Virtual Track @@ -604,7 +753,7 @@ POST /api/release-tracks/new "composition": { "component_tracks": [ { - "track_id": "GroupsMonthly--uuid", + "track_id": "release-track--uuid-1", "resolution_strategy": "latest_tagged", "filters": { "object_types": ["intrusion-set"] @@ -636,11 +785,11 @@ PUT /api/release-tracks/:id/composition { "component_tracks": [ { - "track_id": "GroupsMonthly--uuid", + "track_id": "release-track--uuid-1", "resolution_strategy": "latest_tagged" }, { - "track_id": "TechniquesQuarterly--uuid", + "track_id": "release-track--uuid-2", "resolution_strategy": "specific_version", "version": "2.0" } @@ -710,30 +859,30 @@ GET /api/release-tracks/:id?format=workbench&include=all **Query params:** - `format`: `bundle` | `workbench` | `filesystemstore` -- `include`: `members` | `staged` | `candidates` | `all` +- `include`: `members` | `quarantine` | `all` - `resolve`: `true` (default) | `false` - Whether to resolve composition **Response when `resolve=true`:** ```json { - "stix": { - "id": "x-mitre-collection--virtual-uuid", - "type": "virtual", - "x_mitre_version": null - }, + "id": "release-track--uuid-virtual", + "type": "virtual", + "snapshot_id": "2024-03-05T10:00:00.000Z", + "modified": "2024-03-05T10:00:00Z", + "version": null, + "name": "Enterprise ATT&CK", "resolved_content": { "members": [ { "object_ref": "intrusion-set--APT1", "object_modified": "2024-02-01T10:00:00Z", - "source_track": "GroupsMonthly--uuid", + "source_track": "release-track--uuid-1", "source_version": "5.2" } // ... all resolved objects ], - "staged": [], - "candidates": [] + "quarantine": [] }, "composition_resolution": { @@ -743,45 +892,78 @@ GET /api/release-tracks/:id?format=workbench&include=all } ``` -## Hybrid Model: Virtual Track + Native Objects +## Quarantine Management -Virtual tracks can have **both** composed content **and** native objects: +When using the `quarantine` deduplication strategy, conflicting objects are stored in the virtual track's `quarantine` tier. Users must manually resolve these conflicts: -```javascript +**View quarantined objects:** +```bash +GET /api/release-tracks/:id?include=quarantine +``` + +**Manually promote a quarantined object to members:** +```bash +POST /api/release-tracks/:id/quarantine/promote +``` + +**Request:** +```json { - stix: { - type: "virtual" - }, + "object_ref": "intrusion-set--APT1", + "object_modified": "2024-02-01T10:00:00Z" +} +``` - workspace: { - // Composed from standard tracks - composition: { - component_tracks: [ - { track_id: "GroupsMonthly--uuid" }, - { track_id: "TechniquesQuarterly--uuid" } - ] - }, +**Effect:** +- Moves the specified version from `quarantine` to `members` +- Removes other versions of the same object from `quarantine` +- Next snapshot tagging will include this object in the release - // PLUS virtual track's own candidates/staged - candidates: [ - { - object_ref: "marking-definition--enterprise-only", - object_modified: "2024-01-01T10:00:00Z", - status: "reviewed" - } +## Hybrid Model: Virtual Track + Native Objects + +Virtual tracks can optionally have **native objects** in addition to composed content. This is an advanced use case where a virtual track needs to include objects that don't exist in any component track: + +```javascript +{ + id: "release-track--uuid-virtual", + type: "virtual", + + // Composed from standard tracks + composition: { + component_tracks: [ + { track_id: "release-track--uuid-1", priority: 1 }, + { track_id: "release-track--uuid-2", priority: 2 } ], + deduplication: { + strategy: "prioritize_latest_object" + } + }, - staged: [] - } + // PLUS virtual track's own native members + native_members: [ + { + object_ref: "marking-definition--enterprise-only", + object_modified: "2024-01-01T10:00:00Z" + } + ], + + // Final result after sync + members: [ + // ... objects from component tracks + // ... plus native_members + ], + quarantine: [] } ``` -**Use case:** Enterprise track includes Groups and Techniques from standard tracks, PLUS Enterprise-specific marking definitions or custom objects. +**Use case:** Enterprise track includes Groups and Techniques from standard tracks, PLUS Enterprise-specific marking definitions or custom objects that don't belong in any component track. **When virtual snapshot is created:** -1. Resolve composed content from component tracks -2. Merge with virtual track's native candidates/staged/members -3. Apply deduplication if any native objects overlap with composed objects +1. Resolve composed content from component tracks (goes to `members` or `quarantine`) +2. Merge with virtual track's `native_members` (goes to `members`) +3. If any `native_members` conflict with composed objects, apply deduplication strategy + +**Note:** This is an advanced feature. Most virtual tracks should only use composition without native members. ## Migration Strategy @@ -796,13 +978,13 @@ POST /api/release-tracks/new } # Add existing Groups as candidates -POST /api/release-tracks/GroupsMonthly--uuid/candidates +POST /api/release-tracks/release-track--uuid-1/candidates { "object_refs": ["intrusion-set--APT1", "intrusion-set--APT2", ...] } # Tag initial release -POST /api/release-tracks/GroupsMonthly--uuid/bump +POST /api/release-tracks/release-track--uuid-1/bump { "version": "1.0" } ``` @@ -816,11 +998,11 @@ POST /api/release-tracks/new "composition": { "component_tracks": [ { - "track_id": "GroupsMonthly--uuid", + "track_id": "release-track--uuid-1", "resolution_strategy": "latest_tagged" }, { - "track_id": "TechniquesQuarterly--uuid", + "track_id": "release-track--uuid-2", "resolution_strategy": "latest_tagged" } ] @@ -836,13 +1018,13 @@ POST /api/release-tracks/new ```bash # Manually trigger first snapshot -POST /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/create +POST /api/release-tracks/release-track--uuid-virtual/snapshots/create # Review draft snapshot -GET /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/:modified +GET /api/release-tracks/release-track--uuid-virtual/snapshots/:modified # Tag as Enterprise v14.0 -POST /api/release-tracks/EnterpriseTwiceAnnual--uuid/snapshots/:modified/bump +POST /api/release-tracks/release-track--uuid-virtual/snapshots/:modified/bump { "version": "14.0" } ``` @@ -892,11 +1074,14 @@ Otherwise, return composition metadata without resolving: if (!query.resolve && query.format === 'workbench') { // Return composition config without resolving return { - stix: snapshot.stix, - workspace: { - composition: snapshot.workspace.composition, - composition_resolution: snapshot.workspace.composition_resolution // Pre-computed - } + id: snapshot.id, + type: snapshot.type, + snapshot_id: snapshot.snapshot_id, + modified: snapshot.modified, + version: snapshot.version, + name: snapshot.name, + composition: snapshot.composition, + composition_resolution: snapshot.composition_resolution // Pre-computed }; } ``` @@ -1037,22 +1222,12 @@ Virtual tracks cannot compose from draft snapshots. } ``` -### Error: Circular Dependency - -```json -{ - "error": "CircularDependencyError", - "message": "Virtual track composition creates circular dependency: VirtualA → VirtualB → VirtualA", - "resolution": "Remove one of the component track references to break the cycle" -} -``` - -### Error: Composition Depth Exceeded +### Error: Component Is Virtual Track ```json { - "error": "CompositionDepthExceededError", - "message": "Virtual track composition exceeds maximum depth of 3", - "resolution": "Reduce nesting of virtual tracks" + "error": "InvalidComponentTypeError", + "message": "Virtual tracks can only compose from standard tracks. Component 'release-track--uuid-x' is a virtual track.", + "resolution": "Remove the virtual track from component_tracks. Virtual tracks cannot compose from other virtual tracks." } ``` diff --git a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md index 8b29134f..d2042ddb 100644 --- a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md +++ b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md @@ -241,7 +241,174 @@ POST /api/release-tracks/:id/candidates/promote - Useful for exceptions or urgent fixes - Logs warning for audit trail -### 4. Viewing Latest Snapshot with All Tiers +### 4. Promotion Conflict Resolution + +When promoting objects between tiers, conflicts can occur if multiple versions of the same object (same `stix.id`, different `stix.modified` timestamps) exist. Release tracks use **conflict resolution policies** to determine how to handle these situations. + +**When do conflicts occur?** +- Promoting from `candidates` to `staged` when a different version of the object already exists in `staged` +- Promoting from `staged` to `members` (during tagging/release) when a different version already exists in `members` + +**Promotions can happen via:** +- **Manual promotion** via REST API endpoint (e.g., `POST /api/release-tracks/:id/candidates/promote`) +- **Auto-promotion** based on candidacy threshold (e.g., object status changes to `awaiting-review`) +- **Tagging/release operations** (e.g., `POST /api/release-tracks/:id/bump`) + +#### Conflict Resolution Policies + +Release tracks can be configured with different policies for handling promotion conflicts: + +```javascript +config: { + promotion_conflicts: { + candidates_to_staged: "prefer_latest", // Candidates → Staged promotions + staged_to_members: "abort" // Staged → Members promotions (during release) + } +} +``` + +#### Policy Options + +##### 1. `always_overwrite` + +Always keep the incoming object and discard the incumbent object. + +**Example:** +```javascript +// Current state: +// - staged: attack-pattern--T1234, modified: 2024-01-15 + +// Promotion request: +// - Promote attack-pattern--T1234, modified: 2024-02-20 from candidates to staged + +// Result with always_overwrite: +// - staged: attack-pattern--T1234, modified: 2024-02-20 (new version) +// - candidates: attack-pattern--T1234, modified: 2024-02-20 (removed) +``` + +**Use case:** "Always use the latest work, overwrite previous versions" + +##### 2. `always_reject` + +Always keep the incumbent object and reject the incoming object. The rejected object remains in its current tier. + +**Example:** +```javascript +// Current state: +// - staged: attack-pattern--T1234, modified: 2024-01-15 + +// Promotion request: +// - Promote attack-pattern--T1234, modified: 2024-02-20 from candidates to staged + +// Result with always_reject: +// - staged: attack-pattern--T1234, modified: 2024-01-15 (unchanged) +// - candidates: attack-pattern--T1234, modified: 2024-02-20 (stays in candidates) + +// Response: +{ + "rejected": [ + { + "object_ref": "attack-pattern--T1234", + "object_modified": "2024-02-20T10:00:00Z", + "reason": "Conflict: Different version already in staged tier", + "incumbent_version": "2024-01-15T10:00:00Z", + "resolution": "Rejected per always_reject policy" + } + ] +} +``` + +**Use case:** "Protect already-staged content from being overwritten" + +##### 3. `prefer_latest` + +Keep whichever version has the newer `modified` timestamp. + +**Example:** +```javascript +// Current state: +// - staged: attack-pattern--T1234, modified: 2024-01-15 + +// Promotion request: +// - Promote attack-pattern--T1234, modified: 2024-02-20 from candidates to staged + +// Result with prefer_latest: +// - staged: attack-pattern--T1234, modified: 2024-02-20 (newer version wins) +``` + +**Use case:** "Trust the most recent edits, regardless of current tier" + +##### 4. `abort` (Tagging/Release Operations Only) + +**Only available for `staged_to_members` during tagging/release operations.** + +If a conflict occurs during a tagging/release operation (`POST /api/release-tracks/:id/bump`), reject and abort the entire release. The snapshot will NOT be tagged, and no immutable snapshot will be created. + +**Example:** +```javascript +// Current state: +// - members: attack-pattern--T1234, modified: 2024-01-15 +// - staged: attack-pattern--T1234, modified: 2024-02-20 + +// Tagging request: +POST /api/release-tracks/release-track--123/bump +{ "type": "minor" } + +// Result with abort: +// ERROR Response: +{ + "error": "ReleaseConflictError", + "message": "Cannot complete release due to promotion conflict", + "conflicts": [ + { + "object_ref": "attack-pattern--T1234", + "incumbent_version": "2024-01-15T10:00:00Z", + "incoming_version": "2024-02-20T10:00:00Z", + "tier": "members" + } + ], + "resolution": "Resolve conflicts manually before releasing, or change promotion_conflicts.staged_to_members policy" +} + +// State unchanged: +// - Snapshot NOT tagged +// - No new version history entry +// - Objects remain in current tiers +``` + +**Use case:** "Never accidentally overwrite released content during a release; require explicit conflict resolution" + +**Why abort is important:** Once a snapshot is tagged and released, it becomes immutable. The `abort` policy ensures that releases don't inadvertently overwrite existing released content, providing an additional safety guardrail for critical release operations. + +#### Configuring Conflict Resolution Policies + +**Update release track configuration:** +```bash +PUT /api/release-tracks/:id/config +``` + +**Request:** +```json +{ + "promotion_conflicts": { + "candidates_to_staged": "prefer_latest", + "staged_to_members": "abort" + } +} +``` + +**Default values:** +- `candidates_to_staged`: `"prefer_latest"` +- `staged_to_members`: `"abort"` + +#### Best Practices + +1. **Production tracks**: Use `abort` for `staged_to_members` to prevent accidental overwrites during releases +2. **Development tracks**: Use `always_overwrite` or `prefer_latest` for faster iteration +3. **Review conflicts before releasing**: Always run `GET /api/release-tracks/:id/bump/preview` to identify potential conflicts +4. **Manual resolution**: When `abort` triggers, manually resolve conflicts before retrying the release + +### 5. Viewing Latest Snapshot with All Tiers Set the `include` query parameter to `members`, `staged`, `candidates` or `all` to view different subsets of a given snapshot. ``` @@ -288,7 +455,7 @@ GET /api/release-tracks/:id?include=all } ``` -### 5. Preview Release +### 6. Preview Release Compute a release preview, which outputs a verbose diff of what will change in the next release. ``` @@ -338,7 +505,7 @@ GET /api/release-tracks/:id/bump/preview } ``` -### 6. Bump with Staging +### 7. Bump with Staging ``` POST /api/collections/:id/bump diff --git a/docs/COLLECTIONS_V2/06_ENTITIES.md b/docs/COLLECTIONS_V2/06_ENTITIES.md index 489ab335..0ee1c312 100644 --- a/docs/COLLECTIONS_V2/06_ENTITIES.md +++ b/docs/COLLECTIONS_V2/06_ENTITIES.md @@ -2,21 +2,38 @@ This document tracks new database schemas, interfaces, etc.; as well as changes to any such existing entities. -### Release Track +### Release Track `ReleaseTrack` instances will be tracked as independent MongoDB Collections. The reason for this is because the volume of snapshot permutations is expected to be very high given the frequency of changes that typically occur between releases. -MongoDB Collections will follow a predictive naming convention that the REST API will (attempt to) detect at runtime: -``` -$name--$uuid -``` -`$name` is set by the user whereas `$uuid` is dynamically generated and must be unique. +#### Naming Conventions -For example, a user might create a new release tracked called `Enterprise`; at the time of instantiation, a unique UUIv4 identifier is generated, `8b0ff8f9-27fd-4d7e-bbc9-8fe9465342af`, resulting in the following MongoDB Collection: +**Release Track Names:** +- Must contain only alphanumeric characters and spaces: `[a-zA-Z0-9 ]` +- No special characters allowed (no hyphens, underscores, or other punctuation) +- Examples: `Enterprise`, `Groups Monthly`, `Techniques Quarterly` + +**Release Track IDs:** +MongoDB Collections and release track IDs follow a simple naming convention: ``` -Enterprise--8b0ff8f9-27fd-4d7e-bbc9-8fe9465342af +release-track--$uuid ``` +Where: +- `release-track--` is a fixed prefix +- `$uuid` is a dynamically generated UUIDv4 identifier (must be unique) + +**Example:** +A user creates a release track named `Groups Monthly`: +1. Name: `Groups Monthly` (user-specified, stored in the `name` field) +2. UUID: `8b0ff8f9-27fd-4d7e-bbc9-8fe9465342af` (generated) +3. Final ID: `release-track--8b0ff8f9-27fd-4d7e-bbc9-8fe9465342af` + +This ID is used for: +- MongoDB Collection name +- The `id` field in release track snapshots +- API endpoint references (`/api/release-tracks/:id`) + ### Release Track Types @@ -108,6 +125,10 @@ Each release track snapshot will be tracked as an individual MongoDB Document in include_secondary_objects: { enabled: true, status_threshold: "reviewed" + }, + promotion_conflicts: { + candidates_to_staged: "prefer_latest", // "always_overwrite" | "always_reject" | "prefer_latest" + staged_to_members: "abort" // "always_overwrite" | "always_reject" | "prefer_latest" | "abort" } }, @@ -226,16 +247,15 @@ Virtual release tracks compute their contents by aggregating objects from compon created_by_ref: "identity--uuid", object_marking_refs: ["marking-definition--uuid"], - // Objects in this snapshot (resolved from component tracks) + // Objects in this snapshot (Virtual tracks use 2-tier system) members: [ { object_ref: "intrusion-set--APT1", object_modified: "2024-02-01T10:00:00Z" } - // ... 870 total objects + // ... 870 total objects synced from component tracks ], - staged: [], - candidates: [], + quarantine: [], // Conflicting objects requiring manual resolution // Composition rules - defines how this virtual track is built composition: { @@ -243,6 +263,7 @@ Virtual release tracks compute their contents by aggregating objects from compon { track_id: "release-track--groups-monthly", resolution_strategy: "latest_tagged", // "latest_tagged" | "specific_version" | "specific_snapshot" + priority: 1, // Required for prioritize_higher_priority strategy (lower number = higher priority) // Optional: version/snapshot specification for non-latest strategies version: "5.0", // Used with "specific_version" strategy @@ -258,17 +279,16 @@ Virtual release tracks compute their contents by aggregating objects from compon { track_id: "release-track--techniques-quarterly", resolution_strategy: "latest_tagged", + priority: 2, filters: { object_types: ["attack-pattern"] } } ], - // Deduplication rules when same object appears in multiple components + // Deduplication strategy when same object appears in multiple component tracks deduplication: { - strategy: "prefer_latest_modified", // "prefer_latest_modified" | "prefer_highest_version" | "error" - tier_resolution: "highest_tier", // "highest_tier" | "source_priority" - status_resolution: "highest_status" // "highest_status" | "source_priority" + strategy: "prioritize_latest_object" // "prioritize_latest_object" | "prioritize_latest_snapshot" | "prioritize_higher_priority" | "quarantine" } }, @@ -323,9 +343,7 @@ Virtual release tracks compute their contents by aggregating objects from compon // Native objects (if virtual track has its own objects in addition to composed) native_objects: { - candidates_count: 0, - staged_count: 0, - members_count: 0 + members_count: 0 // Virtual tracks can optionally have native members }, // Final statistics @@ -337,8 +355,7 @@ Virtual release tracks compute their contents by aggregating objects from compon }, by_tier: { "members": 870, - "staged": 0, - "candidates": 0 + "quarantine": 0 } } }, @@ -355,9 +372,7 @@ Virtual release tracks compute their contents by aggregating objects from compon // Configuration config: { - candidacy_threshold: "reviewed", - notification_email: "enterprise-team@example.com", - max_composition_depth: 3 // Limit nesting depth for virtual tracks + notification_email: "enterprise-team@example.com" }, // Version history (same as standard tracks) @@ -379,17 +394,20 @@ Virtual release tracks compute their contents by aggregating objects from compon **Key Differences from Standard Tracks:** 1. **Type Identification**: `stix.type = "virtual"` -2. **Composition Rules**: Defines which component tracks to aggregate and how -3. **Composition Resolution**: Immutable metadata about how snapshot was computed -4. **No Direct Object Management**: Virtual tracks don't add objects as candidates directly (except optional native objects) -5. **Scheduled Snapshots**: Can auto-generate snapshots on schedule -6. **Component Version Tracking**: Version history records which component versions were included +2. **Two-Tier System**: Only `members` and `quarantine` (no `candidates` or `staged` tiers) +3. **Composition Rules**: Defines which component tracks to aggregate and how +4. **Composition Resolution**: Immutable metadata about how snapshot was computed +5. **Sync from Members Only**: Always pulls from component tracks' `members` tier (never staged or candidates) +6. **No Workflow States**: No work-in-progress, awaiting-review, or reviewed states +7. **Scheduled Snapshots**: Can auto-generate snapshots on schedule +8. **Component Version Tracking**: Version history records which component versions were included **Virtual Track Constraints:** - Can only reference **tagged snapshots** from component tracks (not drafts) +- Can only sync from component tracks' **`members` tier** (released objects only) +- Can only compose from **standard release tracks** (not other virtual tracks - no nesting allowed) - Snapshots are created **manually or on schedule** (never event-driven) - All snapshots start as **drafts** and must be explicitly tagged - Component tracks must exist and have at least one tagged release -- Circular dependencies are not allowed (VirtualA → VirtualB → VirtualA) -- Maximum composition depth is configurable (default: 3 levels) \ No newline at end of file +- Each component track must have a unique **priority** value (no duplicates) \ No newline at end of file From 94349384cb57a160146ed1555a3899c63d71dfc6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:13:55 -0500 Subject: [PATCH 177/370] docs(feature-planning): small tweaks and typo corrections --- docs/COLLECTIONS_V2/01_SUMMARY.md | 12 +-- docs/COLLECTIONS_V2/02_TERMINOLOGY.md | 93 ++++++++++++---------- docs/COLLECTIONS_V2/03_VERSIONING.md | 42 +++++----- docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md | 16 ++-- 4 files changed, 85 insertions(+), 78 deletions(-) diff --git a/docs/COLLECTIONS_V2/01_SUMMARY.md b/docs/COLLECTIONS_V2/01_SUMMARY.md index 23c893f3..800f93ca 100644 --- a/docs/COLLECTIONS_V2/01_SUMMARY.md +++ b/docs/COLLECTIONS_V2/01_SUMMARY.md @@ -95,22 +95,22 @@ We borrow heavily concepts from git. Snapshots are sort of like commits and tagg - May be a **draft release** (untagged) or **tagged release** (has version number) **Tagged Releases** (like Git tags) -- Snapshots are tagged with `x_mitre_version`. Draft snapshots are denoted by the fact that their `x_mitre_version` key is set to `null`. -- Uses MAJOR.MINOR versioning (not MAJOR.MINOR.PATCH), as specified by `x_mitre_version` +- Snapshots are tagged with `version`, which when exported/retrieved as a STIX bundle, will be expressed as `x_mitre_version`. Draft snapshots are denoted by the fact that their `version` key is set to `null`. +- Uses MAJOR.MINOR versioning (not MAJOR.MINOR.PATCH), as specified by the [`x_mitre_version` ADM schema](https://github.com/mitre-attack/attack-data-model/blob/f249442b3588de9cca84b819d480306b106d2c1f/src/schemas/common/property-schemas/attack-versioning.ts#L21:L26) - Snapshots are tagged in-place (no duplicate data) -- When a snapshot is tagged/released, an event is captured in its `workspace.version_history` array +- When a snapshot is tagged/released, an event is captured in its `version_history` array - Once a snapshot is tagged, it cannot be re-tagged. Tagged snapshots are **immutable**. ### 3. Three-Tier Workflow Integration with Version Pinning We use the preexisting object workflow statuses, `work-in-progress`, `awaiting-review`, and `reviewed`, to control each object's "standing" in a release track. -There are three types of "standings": +There are three types of membership "standings": 1. **Candidate**: When an object is first added to a release track, is it considered a candidate. It does not have full membership yet; if the snapshot were to be tagged and released right now, candidates would not be included. - 2. **Staged**: Once a candidate's workflow status meets the release track's ["candidacy threshold"](./4_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `x_mitre_contents`. + 2. **Staged**: Once a candidate's workflow status meets the release track's ["candidacy threshold"](./4_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in the resultant bundle's `x_mitre_contents`. 3. **Member**: Objects are considered "members" if they are "cooked" into the `x_mitre_contents` array of the current snapshot. These are considered already released. -Thus, we now have a viable solution for the classic "STIX freeze" dilemma wherein editors cannot begin working on the next-next release until all objects in the next release have been released. Staged objects are locked in for the imminent release, but editors are free to continue iterating on future object changes and can queue them up as candidates without affecting the permutation that has already been staged for the imminent release. +This presents a tenable solution to the classic "STIX freeze" dilemma wherein editors cannot begin working on the next-*next* (e.g., v20) release until all objects in the next (e.g., v19) release have been released. Staged objects are locked in for the imminent release, but editors are free to continue iterating on future object changes and can queue them up as candidates without affecting the permutation that has already been staged for the imminent release. Candidates and staged objects alike can be be statically pinned to specific versions via `stix.id` and `stix.modified` couplings, or maintain dynamic/moving references to object versions by omitting `stix.modified`. In the latter, scenario, the release track will effectively "follow" the latest permutation of the relevant object until the moment a release snapshot is generated, at which point the latest permutation will become "locked in" to `x_mitre_contents` via the `stix.id` and `stix.modified` keys of the latest permutation of the object that existed at the time of the release. diff --git a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md b/docs/COLLECTIONS_V2/02_TERMINOLOGY.md index 14e2721b..43a726c4 100644 --- a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md +++ b/docs/COLLECTIONS_V2/02_TERMINOLOGY.md @@ -24,20 +24,20 @@ This overloading creates confusion in documentation, code, and conversation. Col ### Release Track -A **release track** is a series/chain (linked list) of **snapshots**, where each snapshot is either a draft release or a tagged release. +A **release track** (RT) is a series/chain (linked list) of **snapshots**, where each snapshot is either a draft release or a tagged release. **Technical Definition:** -- A release track is represented by all documents in the database sharing the same `stix.id` +- A release track is represented by all documents in a RT-designated Mongo collection sharing the same `id` + - There exists exactly one Mongo collection for each release track. - Each document in the series represents a snapshot at a specific point in time - The release track provides version control and release management for curated sets of STIX objects **Characteristics:** -- Has a unique identifier (e.g., `x-mitre-release--uuid`) - - **TODO**: should we support custom, user-specified identifiers? As long as we enforce uniqueness, I don't see an issue with abandoning the `type--uuid` format here, since the ID will not be exposed in releases. +- Has a unique identifier (e.g., `release-track--uuid`) (see [naming conventions](./06_ENTITIES.md#naming-conventions) for details) - Contains a chronological history of all changes - Supports Git-inspired versioning workflow -**Types of Release Tracks:** +#### Types of Release Tracks There are two types of release tracks: @@ -48,7 +48,9 @@ There are two types of release tracks: 2. **Virtual Release Track** - Computes content by aggregating other release tracks - No direct object management (objects managed in source tracks) - - Creates snapshots manually or on schedule + - Creates snapshots **manually** or on **schedule** + - **Event-driven snapshots are not supported** to avoid from RT snapshot explosion. Consider for example a virtual RT that composes from 10 standard RTs, each of which releases on a daily basis: an enormous amount of virtual snapshots would quickly accrue, making it difficult to ascertain the causal relationships to the originating snapshots and which should actually be tagged/released. + - Instead, users are encouraged to use virtual release tracks carefully and intentionally, creating aggregate releases on a more controlled, infrequent cadence than their non-virtual counterparts. - Examples: "EnterpriseTwiceAnnual" (aggregates Groups + Techniques + Software) **Examples:** @@ -62,15 +64,15 @@ There are two types of release tracks: ### Snapshot -A **snapshot** is a *node* in the release track's version history, identified by its `stix.modified` timestamp. +A **snapshot** is a *node* in the release track's version history, identified by its `modified` timestamp. **Technical Definition:** -- Each snapshot is a MongoDB document with a specific `stix.id` and `stix.modified` timestamp +- Each snapshot is a MongoDB document with a specific `id` and `modified` timestamp - **Snapshots are immutable** once created (**except** for tagging operations) -- The snapshot identifier is the combination of `stix.id` + `stix.modified` +- The snapshot identifier is the combination of `id` + `modified` **Characteristics:** -- Unique `stix.modified` timestamp (ISO 8601 format) +- Unique `modified` timestamp (ISO 8601 format) - May be a **draft** release or a **tagged** release - Contains the full state of the release track at that point in time - Analogous to a Git commit @@ -91,7 +93,7 @@ A **snapshot** is a *node* in the release track's version history, identified by A **draft release** (or **draft snapshot**) is an untagged snapshot - still in development, not yet published. **Technical Definition:** -- A snapshot where `stix.x_mitre_version === null` +- A snapshot where `version === null` - Represents work-in-progress that has not been marked as production-ready - Can be freely modified (creates new snapshots) without affecting published releases @@ -110,10 +112,10 @@ A **draft release** (or **draft snapshot**) is an untagged snapshot - still in d ### Tagged Release -A **tagged release** (or **tagged snapshot**) is a snapshot that has been marked with a version number (`x_mitre_version`) and is considered published/released. +A **tagged release** (or **tagged snapshot**) is a snapshot that has been marked with a version number (`version`) and is considered published/released. **Technical Definition:** -- A snapshot where `stix.x_mitre_version !== null` +- A snapshot where `version !== null` - The version follows MAJOR.MINOR format (e.g., "1.0", "2.3", "15.1") - Created by performing a tagging operation on a draft release - The `stix.modified` timestamp does not change during tagging (in-place operation) @@ -122,7 +124,7 @@ A **tagged release** (or **tagged snapshot**) is a snapshot that has been marked - Has an explicit version number - Considered production-ready and published - **Immutable** - cannot be re-tagged or untagged -- Recorded in `workspace.version_history` for audit trail +- Recorded in `version_history` for audit trail - Analogous to a Git tag **Examples:** @@ -138,13 +140,12 @@ A **tagged release** (or **tagged snapshot**) is a snapshot that has been marked The **tagging operation** marks an existing snapshot as a tagged release by assigning it a version number. **Technical Definition:** -- Sets `stix.x_mitre_version` on an existing snapshot (in-place update) -- Does NOT create a new snapshot (does NOT change `stix.modified`) -- Adds an entry to `workspace.version_history` for audit trail +- Sets `version` on an existing snapshot (in-place update) +- Does NOT create a new snapshot (does NOT change `modified`) +- Adds an entry to `version_history` for audit trail - Can be performed on the latest snapshot or a specific historical snapshot **Characteristics:** -- Analogous to `git tag` - Version must be greater than all previous tagged releases (monotonically increasing) - Cannot tag a snapshot that is already tagged (throws `AlreadyReleasedError`) - Supports automatic version calculation (MAJOR/MINOR bump) or explicit version @@ -166,7 +167,7 @@ Standard release tracks use three tiers to manage the object lifecycle from deve ##### 1. Candidate Objects -**Location:** `workspace.candidates` +**Location:** `candidates` **Definition:** Objects being worked on; not yet ready for release. @@ -180,7 +181,8 @@ Standard release tracks use three tiers to manage the object lifecycle from deve **Duplicate Rules:** - Cannot contain exact duplicates (same `object_ref` + `object_modified` pair) - **CAN** contain multiple versions of the same object (same `object_ref`, different `object_modified` timestamps) -- Example: Can have `attack-pattern--T1234, modified: 2024-01-15` AND `attack-pattern--T1234, modified: 2024-02-20` simultaneously + - Example: Can have `attack-pattern--T1234, modified: 2024-01-15` AND `attack-pattern--T1234, modified: 2024-02-20` simultaneously + - However, only one version of a given object can be promoted to the `staged` tier and `members` tier **Examples:** - "Add these 10 techniques as candidate objects" @@ -189,15 +191,18 @@ Standard release tracks use three tiers to manage the object lifecycle from deve ##### 2. Staged Objects -**Location:** `workspace.staged` +**Location:** `staged` **Definition:** Objects that have been reviewed (in this release track) and are ready for the next tagged release. **Characteristics:** -- Once a candidate's workflow status meets the release track's ["candidacy threshold"](./05_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `x_mitre_contents`. -- Each entry includes a version pin (`object_modified` timestamp) -- Auto-promoted from candidates when objects meet the candidacy threshold -- Moved to member objects tier (`x_mitre_contents`) when the snapshot is tagged +- Once a candidate's workflow status meets the release track's ["candidacy threshold"](./05_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `members`. + +When the release is exported as a `bundle`, all `members` will be included in the resultant bundle's `x_mitre_contents` array. + +- Each `staged` entry includes a version pin (`object_modified` timestamp), which can either equal an ISO 8601 timestamp (designating a specific object version) or `"latest"` (designating a dynamic reference to the latest permutation of the relevant object) +- Auto-promoted from candidates when objects meet the [candidacy threshold](./05_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) +- Moved to member objects tier (`members`) when the snapshot is tagged - NOT included in published STIX bundles until the snapshot is tagged **Duplicate Rules:** @@ -212,13 +217,13 @@ Standard release tracks use three tiers to manage the object lifecycle from deve ##### 3. Member Objects -**Location:** `stix.x_mitre_contents` +**Location:** `members` **Definition:** Objects included in the current/latest released version of this release track. **Characteristics:** -- Objects are considered "members" if they are "cooked" into the `x_mitre_contents` array of the current snapshot. These are considered already released. -- Each entry is a version-pinned reference (`object_ref` + `object_modified`) +- Objects are considered "members" if they are contained in the `x_mitre_contents` array of the current snapshot. These are considered *already* released. +- Each entry is a version-pinned reference (`object_ref` + `object_modified`). Dynamic references (`object_modified: "latest"`) are not supported on member objects. - These objects are included in published STIX bundles - Represents the production-ready, published content - Only updated when a snapshot is tagged (staged objects are promoted to members) @@ -239,7 +244,7 @@ Virtual release tracks use a simplified two-tier system since they aggregate alr ##### 1. Member Objects -**Location:** `stix.x_mitre_contents` +**Location:** `members` **Definition:** Successfully synced objects from component tracks. @@ -257,7 +262,7 @@ Virtual release tracks use a simplified two-tier system since they aggregate alr ##### 2. Quarantine -**Location:** `workspace.quarantine` +**Location:** `quarantine` **Definition:** Conflicting objects that require manual resolution. @@ -287,7 +292,7 @@ A **virtual release track** is a special type of release track that computes its - Does NOT manage objects through candidate/staged/released workflow - Aggregates content from **component tracks** (standard or other virtual tracks) - Only references **tagged snapshots** from component tracks (never drafts) -- Creates snapshots **manually** or **on schedule** (never event-driven) +- Creates snapshots **manually** or **on schedule** (*never* event-driven; see [Types of Release Tracks](#types-of-release-tracks) for explanation) - All snapshots start as drafts and must be explicitly tagged - Can optionally have **native objects** in addition to composed content (hybrid model) @@ -307,7 +312,7 @@ A **virtual release track** is a special type of release track that computes its A **component track** is a release track (standard or virtual) that is referenced by a virtual release track. **Technical Definition:** -- A component track is specified in a virtual track's `workspace.composition.component_tracks` array +- A component track is specified in a virtual track's `composition.component_tracks` array - Each component defines a `resolution_strategy` (how to select which snapshot to use) - Each component can optionally specify `filters` (which objects to include) @@ -328,7 +333,7 @@ A **component track** is a release track (standard or virtual) that is reference **Composition** refers to the rules and configuration that define how a virtual release track aggregates content from component tracks. **Technical Definition:** -- Defined in `workspace.composition` object of virtual track +- Defined in `composition` object of virtual track - Specifies which component tracks to include - Defines resolution strategies, filters, and deduplication rules @@ -368,7 +373,7 @@ A **component track** is a release track (standard or virtual) that is reference **Characteristics:** - Resolution happens at snapshot creation time (not query time) -- Resolution metadata is stored in snapshot (`workspace.composition_resolution`) +- Resolution metadata is stored in snapshot (`composition_resolution`) - Once resolved, a snapshot's composition is immutable - Different snapshots of same virtual track may resolve to different component versions @@ -413,21 +418,21 @@ A **resolution strategy** determines which snapshot from a component track to us | Collection version | Snapshot | Individual node in version history | | Collection (unpublished) | Draft Release | Snapshot without version number | | Collection (published) | Tagged Release | Snapshot with version number | -| Collection contents | Member objects | Objects in `stix.x_mitre_contents` | -| Collection Bundle | -- | STIX bundle exported from release track | +| Collection contents | Member objects | Objects in `members` | +| Collection Bundle | -- | STIX bundle exported from a released/tagged snapshot | ### Technical Mappings | Concept | MongoDB Representation | |---------|------------------------| -| Release Track | All docs with same `stix.id` | -| Snapshot | Doc with specific `stix.id` + `stix.modified` | -| Draft Release | Snapshot where `x_mitre_version === null` | -| Tagged Release | Snapshot where `x_mitre_version !== null` | -| Tagging Operation | Set `x_mitre_version` on existing doc | -| Candidate Objects | Array at `workspace.candidates` | -| Staged Objects | Array at `workspace.staged` | -| Member Objects | Array at `stix.x_mitre_contents` | +| Release Track | Mongo collection following name format `$name--$uuid` | +| Snapshot | Doc with specific `id` + `modified` | +| Draft Release | Snapshot where `version === null` | +| Tagged Release | Snapshot where `version !== null` | +| Tagging Operation | Set `version` on existing doc | +| Candidate Objects | Array at `candidates` | +| Staged Objects | Array at `staged` | +| Member Objects | Array at `members` | --- diff --git a/docs/COLLECTIONS_V2/03_VERSIONING.md b/docs/COLLECTIONS_V2/03_VERSIONING.md index f59a31aa..b3f3447b 100644 --- a/docs/COLLECTIONS_V2/03_VERSIONING.md +++ b/docs/COLLECTIONS_V2/03_VERSIONING.md @@ -16,49 +16,49 @@ This approach allows continuous development while providing stable, versioned re ### Snapshots A **snapshot** is an immutable state of a release track at a specific point in time, identified by: -- `stix.id` - The release track's STIX identifier (constant across all snapshots) -- `stix.modified` - ISO 8601 timestamp when the snapshot was created (unique per snapshot) +- `id` - The release track's STIX identifier (constant across all snapshots) +- `modified` - ISO 8601 timestamp when the snapshot was created (unique per snapshot) -Every modification operation creates a new snapshot with a new `stix.modified` timestamp. +Every modification operation creates a new snapshot with a new `modified` timestamp. A snapshot may be either a **draft release** (untagged) or a **tagged release** (has version number). ### Draft Releases vs Tagged Releases -A **draft release** is a snapshot without a version number (`x_mitre_version === null`). It represents work-in-progress. +A **draft release** is a snapshot without a version number (`version === null`). It represents work-in-progress. A **tagged release** is a snapshot that has been marked as production-ready for publication, identified by: -- `stix.x_mitre_version` - Version string in MAJOR.MINOR format (e.g., "1.0") +- `version` - Version string in MAJOR.MINOR format (e.g., "1.0") -**Note:** ATT&CK release tracks use a two-part versioning scheme (MAJOR.MINOR), not the three-part semver format (MAJOR.MINOR.PATCH). The patch component is not tracked in `x_mitre_version`. +**Note:** ATT&CK release tracks use a two-part versioning scheme (MAJOR.MINOR), not the three-part semver format (MAJOR.MINOR.PATCH). The patch component is not tracked in `version`. -Not all snapshots are tagged releases. Only snapshots explicitly tagged via the `tag` operation become tagged releases. +Not all snapshots are tagged releases. Only snapshots explicitly tagged via the **bump** operation become tagged releases. **Example Timeline with Tagged Releases:** ``` -id: "release-track--123", snapshot_id: "2024-01-01T10:00:00.000Z" +id: "release-track--123", modified: "2024-01-01T10:00:00.000Z" version: null ← DRAFT RELEASE (work in progress) -id: "release-track--123", snapshot_id: "2024-01-02T14:30:00.000Z" +id: "release-track--123", modified: "2024-01-02T14:30:00.000Z" version: null ← DRAFT RELEASE (work in progress) -id: "release-track--123", snapshot_id: "2024-01-05T09:15:00.000Z" +id: "release-track--123", modified: "2024-01-05T09:15:00.000Z" version: "1.0" ← TAGGED RELEASE (via tagging operation) version_history: [{ version: "1.0", tagged_at: "2024-01-05T10:00:00Z", tagged_by: "user@example.com", - snapshot_id: "2024-01-05T09:15:00.000Z" + modified: "2024-01-05T09:15:00.000Z" }] -id: "release-track--123", snapshot_id: "2024-01-10T11:00:00.000Z" +id: "release-track--123", modified: "2024-01-10T11:00:00.000Z" version: null ← DRAFT RELEASE (more development) -id: "release-track--123", snapshot_id: "2024-01-15T16:20:00.000Z" +id: "release-track--123", modified: "2024-01-15T16:20:00.000Z" version: "1.1" ← TAGGED RELEASE (via tagging operation) version_history: [ - { version: "1.1", tagged_at: "2024-01-15T17:00:00Z", tagged_by: "user@example.com", snapshot_id: "2024-01-15T16:20:00.000Z" }, - { version: "1.0", tagged_at: "2024-01-05T10:00:00Z", tagged_by: "user@example.com", snapshot_id: "2024-01-05T09:15:00.000Z" } + { version: "1.1", tagged_at: "2024-01-15T17:00:00Z", tagged_by: "user@example.com", modified: "2024-01-15T16:20:00.000Z" }, + { version: "1.0", tagged_at: "2024-01-05T10:00:00Z", tagged_by: "user@example.com", modified: "2024-01-05T09:15:00.000Z" } ] ``` @@ -69,8 +69,8 @@ id: "release-track--123", snapshot_id: "2024-01-15T16:20:00.000Z" The `tag` operation **tags an existing snapshot as a release** by assigning it a semantic version number (without the patch number). It does **NOT** create a new snapshot. This is analogous to Git's tagging system: -- Git commits = release track snapshots (identified by `stix.modified`) -- Git tags = tagged releases (identified by `stix.x_mitre_version`) +- Git commits = release track snapshots (identified by `modified` key) +- Git tags = tagged releases (identified by `version` key) ### In-Place Tagging Strategy @@ -94,7 +94,7 @@ When you tag a snapshot: POST /api/release-tracks/:id/bump ``` -Tags the most recent snapshot (highest `stix.modified`) as a tagged release. +Tags the most recent snapshot (highest `modified`) as a tagged release. **Request Body (optional):** ```json @@ -116,7 +116,7 @@ POST /api/release-tracks/release--123/bump } ``` -2. **Major version increment:** +1. **Major version increment:** ```bash # Current latest tagged release: 1.2 # Tag as: 2.0 (major increment) @@ -126,7 +126,7 @@ POST /api/release-tracks/release--123/bump } ``` -3. **Explicit version:** +1. **Explicit version:** ```bash # Set specific version (must be greater than previous) POST /api/release-tracks/release--123/bump @@ -135,7 +135,7 @@ POST /api/release-tracks/release--123/bump } ``` -4. **Default behavior (no body):** +1. **Default behavior (no body):** ```bash # Defaults to minor increment POST /api/release-tracks/release--123/bump diff --git a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md index d2042ddb..5be789b1 100644 --- a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md +++ b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md @@ -33,22 +33,24 @@ Each tier entry includes **version pinning** via the `object_modified` timestamp Release tracks maintain objects in three distinct tiers, with each entry pinning to a specific object version: -1. **Candidates** (`workspace.candidates`) - Objects being worked on with track-scoped status -2. **Staged** (`workspace.staged`) - Reviewed objects (in this release track) ready for the next tagged release -3. **Released** (`stix.x_mitre_contents`) - Object versions included in the current/latest tagged release +1. **Candidates** (`candidates`) - Objects being worked on with track-scoped status +2. **Staged** (`staged`) - Reviewed objects (in this release track) ready for the next tagged release +3. **Released** (`members`) - Object versions included in the current/latest tagged release ### Automatic Promotion Flow ``` Object version added to release track ↓ -Track-scoped status: work-in-progress → Added to workspace.candidates with version pin +Track-scoped status: work-in-progress → Added to candidates with version pin ↓ -Track-scoped status: awaiting-review → Remains in workspace.candidates +Track-scoped status: awaiting-review → Remains in candidates ↓ -Track-scoped status: reviewed → Automatically promoted to workspace.staged +Track-scoped status: reviewed → Automatically promoted to staged ↓ -Snapshot tagged → workspace.staged entries moved to stix.x_mitre_contents +Snapshot tagged → staged entries moved to members + ↓ +Snapshot exported → members reflected in stix.x_mitre_contents of the output bundle ``` ### STIX Freeze Solution From 2692d01c99c7541f520a637d2cd7e22b92c7c621 Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 30 Jan 2026 15:33:08 -0500 Subject: [PATCH 178/370] feat: update validation to enable running .partial() with refinements --- app/services/system/validate-service.js | 48 ++++++++++++++++++++----- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index 895bb83e..dcae4c51 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -3,6 +3,7 @@ const { tacticSchema, techniqueSchema, + techniquePartialSchema, groupSchema, malwareSchema, toolSchema, @@ -17,16 +18,21 @@ const { relationshipSchema, collectionSchema, markingDefinitionSchema, -} = require('@mitre-attack/attack-data-model'); + relationshipPartialSchema, +} = require('@mitre-attack/attack-data-model/dist'); +const { campaignPartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/campaign.schema'); +const { groupPartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/group.schema'); +const { malwarePartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/malware.schema'); +const { toolPartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/tool.schema'); const STIX_SCHEMAS = { 'x-mitre-tactic': tacticSchema, - 'attack-pattern': techniqueSchema, - 'intrusion-set': groupSchema, - malware: malwareSchema, - tool: toolSchema, - campaign: campaignSchema, - relationship: relationshipSchema, + 'attack-pattern': {'partial': techniquePartialSchema, 'full': techniqueSchema}, + 'intrusion-set': {'partial': groupPartialSchema, 'full': groupSchema}, + malware: {'partial': malwarePartialSchema, 'full': malwareSchema}, + tool: {'partial': toolPartialSchema, 'full': toolSchema}, + campaign: {'partial': campaignPartialSchema, 'full': campaignSchema}, + relationship: {'partial': relationshipPartialSchema, 'full': relationshipSchema}, 'course-of-action': mitigationSchema, 'marking-definition': markingDefinitionSchema, 'x-mitre-asset': assetSchema, @@ -94,6 +100,30 @@ const ERROR_TRANSFORMATION_RULES = [ ]; exports.ERROR_TRANSFORMATION_RULES = ERROR_TRANSFORMATION_RULES; +/** + * Get the schema to use for validating a STIX object. + * + * Some STIX types define both a "partial" (work-in-progress) and "full" schema, + * while others only define a single schema. This helper resolves the correct + * schema based on the STIX type and object status. + * + * @param {string} type - The STIX `type` being validated (e.g. "attack-pattern") + * @param {string} status - The object status (e.g. "work-in-progress", "awaiting-review", "reviewed") + * @returns {Object} Zod schema + */ +function getSchema(type, status) { + const entry = STIX_SCHEMAS[type]; + if (!entry) return null; + + if (entry.partial && entry.full) { + return status === 'work-in-progress' ? entry.partial : entry.full; + } + + else{ + return status === 'work-in-progress' ? entry.partial() : entry; + } +} + /** * Check if a validation error should be transformed (converted to warning or suppressed) * @param {Object} error - The validation error from Zod @@ -218,8 +248,8 @@ exports.validateStixObject = function (payload) { }; } - // Apply partial validation for work-in-progress - const stixSchema = status === 'work-in-progress' ? baseSchema.partial() : baseSchema; + // Get the schema to run + const stixSchema = getSchema(type, status); // Validate STIX data const stixResult = stixSchema.safeParse(stix); From d063883bf64ddf6f605703f28002e832aebfdcd3 Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 30 Jan 2026 15:34:00 -0500 Subject: [PATCH 179/370] chore: fix identation --- app/services/system/validate-service.js | 32 +++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index dcae4c51..00c79da5 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -20,19 +20,27 @@ const { markingDefinitionSchema, relationshipPartialSchema, } = require('@mitre-attack/attack-data-model/dist'); -const { campaignPartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/campaign.schema'); -const { groupPartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/group.schema'); -const { malwarePartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/malware.schema'); -const { toolPartialSchema } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/tool.schema'); +const { + campaignPartialSchema, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/campaign.schema'); +const { + groupPartialSchema, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/group.schema'); +const { + malwarePartialSchema, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/malware.schema'); +const { + toolPartialSchema, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/tool.schema'); const STIX_SCHEMAS = { 'x-mitre-tactic': tacticSchema, - 'attack-pattern': {'partial': techniquePartialSchema, 'full': techniqueSchema}, - 'intrusion-set': {'partial': groupPartialSchema, 'full': groupSchema}, - malware: {'partial': malwarePartialSchema, 'full': malwareSchema}, - tool: {'partial': toolPartialSchema, 'full': toolSchema}, - campaign: {'partial': campaignPartialSchema, 'full': campaignSchema}, - relationship: {'partial': relationshipPartialSchema, 'full': relationshipSchema}, + 'attack-pattern': { partial: techniquePartialSchema, full: techniqueSchema }, + 'intrusion-set': { partial: groupPartialSchema, full: groupSchema }, + malware: { partial: malwarePartialSchema, full: malwareSchema }, + tool: { partial: toolPartialSchema, full: toolSchema }, + campaign: { partial: campaignPartialSchema, full: campaignSchema }, + relationship: { partial: relationshipPartialSchema, full: relationshipSchema }, 'course-of-action': mitigationSchema, 'marking-definition': markingDefinitionSchema, 'x-mitre-asset': assetSchema, @@ -117,9 +125,7 @@ function getSchema(type, status) { if (entry.partial && entry.full) { return status === 'work-in-progress' ? entry.partial : entry.full; - } - - else{ + } else { return status === 'work-in-progress' ? entry.partial() : entry; } } From d268e21837b6286937f17ad25faac8b8977cda5a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:48:04 -0500 Subject: [PATCH 180/370] feat(release-tracks): implement mongoose models for release track registry and snapshots --- app/models/release-tracks/model-factory.js | 73 ++++ .../release-track-registry-model.js | 99 +++++ .../release-track-snapshot-schema.js | 371 ++++++++++++++++++ 3 files changed, 543 insertions(+) create mode 100644 app/models/release-tracks/model-factory.js create mode 100644 app/models/release-tracks/release-track-registry-model.js create mode 100644 app/models/release-tracks/release-track-snapshot-schema.js diff --git a/app/models/release-tracks/model-factory.js b/app/models/release-tracks/model-factory.js new file mode 100644 index 00000000..e4ba1771 --- /dev/null +++ b/app/models/release-tracks/model-factory.js @@ -0,0 +1,73 @@ +'use strict'; + +const mongoose = require('mongoose'); +const logger = require('../../lib/logger'); +const { releaseTrackSnapshotSchema } = require('./release-track-snapshot-schema'); + +/** + * ModelFactory manages dynamic Mongoose model creation and caching for release tracks. + * + * Each release track gets its own MongoDB collection (named by track_id, e.g. "release-track--"). + * This factory creates Mongoose models on demand and caches them so that repeated access + * to the same track reuses the same compiled model. + */ +class ModelFactory { + constructor() { + /** @type {Map} */ + this._cache = new Map(); + } + + /** + * Get or create a cached Mongoose model for a release track collection. + * + * @param {string} trackId - The release track ID (e.g. "release-track--"), + * which is also used as the MongoDB collection name. + * @returns {mongoose.Model} The Mongoose model bound to the track's collection. + */ + getModel(trackId) { + if (this._cache.has(trackId)) { + return this._cache.get(trackId); + } + + // Mongoose model names must be unique per connection. Use the trackId directly + // since it's already globally unique (release-track--). + const model = mongoose.model(trackId, releaseTrackSnapshotSchema, trackId); + this._cache.set(trackId, model); + + logger.verbose(`ModelFactory: Created model for collection "${trackId}"`); + return model; + } + + /** + * Remove a cached model. Call this when a release track is deleted + * so the model doesn't linger in memory. + * + * @param {string} trackId - The release track ID to remove from cache. + */ + removeModel(trackId) { + if (this._cache.has(trackId)) { + // Remove from Mongoose's internal model registry + delete mongoose.connection.models[trackId]; + this._cache.delete(trackId); + logger.verbose(`ModelFactory: Removed model for collection "${trackId}"`); + } + } + + /** + * Ensure indexes are created on a release track's collection. + * Call this after creating a new track to build the indexes defined in the schema. + * + * @param {string} trackId - The release track ID. + * @returns {Promise} + */ + async ensureIndexes(trackId) { + const model = this.getModel(trackId); + await model.ensureIndexes(); + logger.verbose(`ModelFactory: Ensured indexes for collection "${trackId}"`); + } +} + +// Singleton instance -- shared across the application +const modelFactory = new ModelFactory(); + +module.exports = modelFactory; diff --git a/app/models/release-tracks/release-track-registry-model.js b/app/models/release-tracks/release-track-registry-model.js new file mode 100644 index 00000000..0aa5c8fd --- /dev/null +++ b/app/models/release-tracks/release-track-registry-model.js @@ -0,0 +1,99 @@ +'use strict'; + +const mongoose = require('mongoose'); + +// --- Validation patterns --- + +const TRACK_ID_RE = /^release-track--[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; +const TRACK_NAME_RE = /^[a-zA-Z0-9 ]+$/; +const VERSION_RE = /^\d+\.\d+$/; +const CRON_RE = /^(\S+\s+){4}\S+$/; + +// --- Sub-schemas --- + +const snapshotScheduleDefinition = { + mode: { + type: String, + enum: ['manual', 'cron', 'dates'], + default: 'manual', + }, + cron: { + type: String, + validate: { + validator: (v) => CRON_RE.test(v), + message: (props) => `"${props.value}" is not a valid cron expression (expected 5 fields)`, + }, + }, + dates: { type: [Date], default: undefined }, +}; +const snapshotScheduleSchema = new mongoose.Schema(snapshotScheduleDefinition, { _id: false }); + +// --- Registry document definition --- + +const releaseTrackRegistryDefinition = { + track_id: { + type: String, + required: [true, 'Release track ID is required'], + index: { unique: true }, + validate: { + validator: (v) => TRACK_ID_RE.test(v), + message: (props) => + `"${props.value}" is not a valid release track ID (expected "release-track--")`, + }, + }, + type: { + type: String, + enum: ['standard', 'virtual'], + required: true, + }, + name: { + type: String, + required: [true, 'Release track name is required'], + validate: { + validator: (v) => TRACK_NAME_RE.test(v), + message: (props) => + `"${props.value}" is not a valid release track name (only alphanumeric characters and spaces allowed)`, + }, + }, + description: { type: String }, + + // Denormalized for fast listing (updated on each snapshot/tag) + latest_snapshot_modified: { type: Date }, + latest_tagged_version: { + type: String, + default: null, + validate: { + validator: (v) => v === null || VERSION_RE.test(v), + message: (props) => + `"${props.value}" is not a valid version (expected MAJOR.MINOR format, e.g. "1.0")`, + }, + }, + snapshot_count: { type: Number, default: 0 }, + tagged_release_count: { type: Number, default: 0 }, + + // Virtual tracks only + snapshot_schedule: { type: snapshotScheduleSchema, default: undefined }, + + created_at: { type: Date, required: true }, + updated_at: { type: Date, required: true }, +}; + +// --- Schema creation --- + +const releaseTrackRegistrySchema = new mongoose.Schema(releaseTrackRegistryDefinition, { + collection: 'releaseTrackRegistry', + bufferCommands: false, +}); + +// --- Indexes --- + +releaseTrackRegistrySchema.index({ type: 1 }); + +// --- Model creation --- + +const ReleaseTrackRegistryModel = mongoose.model( + 'ReleaseTrackRegistry', + releaseTrackRegistrySchema, +); + +module.exports = ReleaseTrackRegistryModel; diff --git a/app/models/release-tracks/release-track-snapshot-schema.js b/app/models/release-tracks/release-track-snapshot-schema.js new file mode 100644 index 00000000..f1f63f54 --- /dev/null +++ b/app/models/release-tracks/release-track-snapshot-schema.js @@ -0,0 +1,371 @@ +'use strict'; + +const mongoose = require('mongoose'); +const { z } = require('zod'); +const { + stixIdentifierSchema, + xMitreVersionSchema, + createStixIdValidator, +} = require('@mitre-attack/attack-data-model'); + +// ============================================================================= +// Custom Mongoose validators +// For context, read: https://mongoosejs.com/docs/validation.html#custom-validators +// ============================================================================= + +// We can't use ADM to validate `track_id` because the ADM's `stixIdentifierSchema` and `createStixIdValidator` +// only supports official STIX types. +// - 'attack-pattern--$uuid' will work +// - 'release-track--$uuid' will NOT work +// TODO we should consider adding a `customStixIdValidator` schema + factoryfunction for checking IDs that follow the STIX ID format but have custom STIX types, e.g., "foobar--$uuid" +const TRACK_ID_RE = /^release-track--[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; +const validateTrackId = { + validator: (v) => TRACK_ID_RE.test(v), + message: (props) => + `"${props.value}" is not a valid release track ID (expected "release-track--")`, +}; + +const validateStixId = { + validator: (v) => stixIdentifierSchema.safeParse(v).success, + message: (props) => `"${props.data}" is not a valid STIX ID (expected "--")`, +}; + +const validateVersion = { + validator: (v) => v === null || xMitreVersionSchema.safeParse(v).success, + message: (props) => + `"${props.data}" is not a valid version (expected MAJOR.MINOR format, e.g. "1.0")`, +}; + +// ============================================================================= +// Sub-schemas (all use _id: false to match codebase conventions) +// ============================================================================= + +// --- Tier entry sub-schemas --- + +const memberEntryDefinition = { + object_ref: { + type: String, + required: true, + validate: validateStixId, + }, + object_modified: { type: Date, required: true }, +}; +const memberEntrySchema = new mongoose.Schema(memberEntryDefinition, { _id: false }); + +const stagedEntryDefinition = { + object_ref: { + type: String, + required: true, + validate: validateStixId, + }, + object_modified: { type: Date, required: true }, + object_status: { + type: String, + enum: ['work-in-progress', 'awaiting-review', 'reviewed'], + required: true, + }, + object_staged_at: { type: Date, required: true }, + object_staged_by: { type: String, required: true }, +}; +const stagedEntrySchema = new mongoose.Schema(stagedEntryDefinition, { _id: false }); + +const candidateEntryDefinition = { + object_ref: { + type: String, + required: true, + validate: validateStixId, + }, + object_modified: { type: Date, required: true }, + object_status: { + type: String, + enum: ['work-in-progress', 'awaiting-review', 'reviewed'], + required: true, + }, + object_added_at: { type: Date, required: true }, + object_added_by: { type: String, required: true }, +}; +const candidateEntrySchema = new mongoose.Schema(candidateEntryDefinition, { _id: false }); + +const quarantineEntryDefinition = { + object_ref: { + type: String, + required: true, + validate: validateStixId, + }, + object_modified: { type: Date, required: true }, + source_track_id: { + type: String, + required: true, + validate: validateTrackId, + }, + source_track_name: { type: String, required: true }, + source_snapshot_version: { + type: String, + validate: validateVersion, + }, + conflict_reason: { type: String, required: true }, +}; +const quarantineEntrySchema = new mongoose.Schema(quarantineEntryDefinition, { _id: false }); + +// --- Composition sub-schemas (virtual tracks) --- + +const componentTrackFiltersDefinition = { + object_types: { type: [String], default: undefined }, + domains: { type: [String], default: undefined }, +}; +const componentTrackFiltersSchema = new mongoose.Schema(componentTrackFiltersDefinition, { + _id: false, +}); + +const componentTrackDefinition = { + track_id: { + type: String, + required: true, + validate: validateTrackId, + }, + resolution_strategy: { + type: String, + enum: ['latest_tagged', 'specific_version', 'specific_snapshot'], + required: true, + }, + priority: { type: Number, required: true }, + version: { + type: String, + validate: validateVersion, + }, + snapshot: { type: Date }, + filters: { type: componentTrackFiltersSchema, default: undefined }, +}; +const componentTrackSchema = new mongoose.Schema(componentTrackDefinition, { _id: false }); + +const compositionDefinition = { + component_tracks: { type: [componentTrackSchema], default: undefined }, + deduplication: { + strategy: { + type: String, + enum: [ + 'prioritize_latest_object', + 'prioritize_latest_snapshot', + 'prioritize_higher_priority', + 'quarantine', + ], + }, + }, +}; +const compositionSchema = new mongoose.Schema(compositionDefinition, { _id: false }); + +// --- Composition resolution sub-schemas (virtual tracks) --- + +const componentSnapshotResolutionDefinition = { + track_id: { + type: String, + required: true, + validate: validateTrackId, + }, + track_name: { type: String, required: true }, + track_type: { type: String, required: true }, + resolved_snapshot_id: { type: Date, required: true }, + resolved_version: { + type: String, + validate: validateVersion, + }, + strategy_used: { type: String, required: true }, + filters_applied: { type: componentTrackFiltersSchema, default: undefined }, + total_objects_in_source: { type: Number, required: true }, + objects_after_filter: { type: Number, required: true }, + objects_contributed: { type: Number, required: true }, +}; +const componentSnapshotResolutionSchema = new mongoose.Schema( + componentSnapshotResolutionDefinition, + { _id: false }, +); + +const deduplicationReportDefinition = { + total_objects_before: { type: Number }, + total_objects_after: { type: Number }, + duplicates_found: { type: Number }, + conflicts_resolved: { type: [mongoose.Schema.Types.Mixed], default: undefined }, +}; +const deduplicationReportSchema = new mongoose.Schema(deduplicationReportDefinition, { + _id: false, +}); + +const compositionResolutionDefinition = { + resolved_at: { type: Date }, + component_snapshots: { type: [componentSnapshotResolutionSchema], default: undefined }, + deduplication: { type: deduplicationReportSchema, default: undefined }, + summary: { type: mongoose.Schema.Types.Mixed, default: undefined }, +}; +const compositionResolutionSchema = new mongoose.Schema(compositionResolutionDefinition, { + _id: false, +}); + +// --- Config sub-schemas --- + +const promotionConflictsDefinition = { + candidates_to_staged: { + type: String, + enum: ['always_overwrite', 'always_reject', 'prefer_latest'], + default: 'prefer_latest', + }, + staged_to_members: { + type: String, + enum: ['always_overwrite', 'always_reject', 'prefer_latest', 'abort'], + default: 'abort', + }, +}; +const promotionConflictsSchema = new mongoose.Schema(promotionConflictsDefinition, { _id: false }); + +const includeSecondaryObjectsDefinition = { + enabled: { type: Boolean, default: true }, + status_threshold: { + type: String, + enum: ['work-in-progress', 'awaiting-review', 'reviewed'], + default: 'reviewed', + }, +}; +const includeSecondaryObjectsSchema = new mongoose.Schema(includeSecondaryObjectsDefinition, { + _id: false, +}); + +const configDefinition = { + candidacy_threshold: { + type: String, + enum: ['work-in-progress', 'awaiting-review', 'reviewed'], + default: 'reviewed', + }, + auto_promote: { type: Boolean, default: true }, + include_candidates_in_snapshots: { type: Boolean, default: false }, + include_secondary_objects: { type: includeSecondaryObjectsSchema, default: undefined }, + promotion_conflicts: { + type: promotionConflictsSchema, + default: () => ({}), + }, +}; +const configSchema = new mongoose.Schema(configDefinition, { _id: false }); + +// --- Version history sub-schema --- + +const versionHistoryEntryDefinition = { + version: { + type: String, + required: true, + validate: validateVersion, + }, + tagged_at: { type: Date, required: true }, + tagged_by: { type: String, required: true }, + snapshot_id: { type: Date, required: true }, + summary: { + members_count: { type: Number }, + promoted_count: { type: Number }, + staged_count: { type: Number }, + candidate_count: { type: Number }, + }, + // Virtual tracks only: records which component versions were included + component_versions: { type: mongoose.Schema.Types.Mixed, default: undefined }, +}; +const versionHistoryEntrySchema = new mongoose.Schema(versionHistoryEntryDefinition, { + _id: false, +}); + +// ============================================================================= +// Main snapshot schema +// ============================================================================= + +const releaseTrackSnapshotDefinition = { + // Identity + id: { + type: String, + required: [true, 'Release track ID is required'], + validate: validateTrackId, + }, + type: { + type: String, + enum: ['standard', 'virtual'], + required: true, + }, + + // Snapshot metadata + modified: { type: Date, required: true }, + version: { + type: String, + default: null, + validate: validateVersion, + }, + + // Release track metadata + name: { + type: String, + required: [true, 'Release track name is required'], + validate: { + validator: (v) => z.string().safeParse(v).success, + message: (props) => + `"${props.value}" is not a valid release track name (only alphanumeric characters and spaces allowed)`, + }, + }, + description: { type: String }, + created: { type: Date, required: true }, + created_by_ref: { + type: String, + validate: { + validator: (v) => createStixIdValidator('identity').safeParse(v).success, + message: (props) => + `"${props.data}" is not a valid identity reference (expected "identity--")`, + }, + }, + object_marking_refs: { + type: [String], + default: undefined, + validate: { + validator: (v) => + v.every((ref) => createStixIdValidator('marking-definition').safeParse(ref).success), + message: () => + 'Each marking reference must be a valid marking-definition ID (expected "marking-definition--")', + }, + }, + + // --- Standard track tiers --- + members: { type: [memberEntrySchema], default: [] }, + staged: { type: [stagedEntrySchema], default: undefined }, + candidates: { type: [candidateEntrySchema], default: undefined }, + + // --- Virtual track tiers --- + quarantine: { type: [quarantineEntrySchema], default: undefined }, + + // --- Virtual track composition --- + composition: { type: compositionSchema, default: undefined }, + composition_resolution: { type: compositionResolutionSchema, default: undefined }, + + // --- Shared --- + config: { type: configSchema, default: () => ({}) }, + version_history: { type: [versionHistoryEntrySchema], default: [] }, +}; + +const releaseTrackSnapshotSchema = new mongoose.Schema(releaseTrackSnapshotDefinition, { + bufferCommands: false, +}); + +// --- Indexes --- + +// Primary lookup: find snapshot by track id + modified timestamp +releaseTrackSnapshotSchema.index({ id: 1, modified: -1 }, { unique: true }); + +// Find the latest tagged version +releaseTrackSnapshotSchema.index({ id: 1, version: 1 }); + +// ============================================================================= +// Exports +// ============================================================================= + +module.exports = { + releaseTrackSnapshotSchema, + // Export sub-schemas for use in tests or other contexts + memberEntrySchema, + stagedEntrySchema, + candidateEntrySchema, + quarantineEntrySchema, + compositionSchema, + compositionResolutionSchema, + configSchema, + versionHistoryEntrySchema, +}; From 2ac0ff5ca9bd3f5eca46aeedbfdd9c498a0520be Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:37:20 -0500 Subject: [PATCH 181/370] refactor(release-tracks): organize zod schemas and mongoose middlewares into distinct modules --- .../release-tracks/release-track-schemas.js | 165 ++++++++++++++++++ .../release-track-validators.js | 79 +++++++++ .../release-track-snapshot-schema.js | 59 ++----- 3 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 app/lib/release-tracks/release-track-schemas.js create mode 100644 app/lib/release-tracks/release-track-validators.js diff --git a/app/lib/release-tracks/release-track-schemas.js b/app/lib/release-tracks/release-track-schemas.js new file mode 100644 index 00000000..d4c8f931 --- /dev/null +++ b/app/lib/release-tracks/release-track-schemas.js @@ -0,0 +1,165 @@ +'use strict'; + +// ============================================================================= +// Zod schemas for release track data validation. +// +// These schemas are the canonical source of truth for release track field +// formats. They are pure Zod schemas with no framework coupling, so they can +// be reused in any context: Mongoose model validators, controller request +// validation, test assertions, etc. +// +// For Mongoose-specific validator wrappers see ./release-track-validators.js. +// ============================================================================= + +const { z } = require('zod'); +const { + stixIdentifierSchema, + xMitreVersionSchema, + createStixIdValidator, +} = require('@mitre-attack/attack-data-model'); + +// ----------------------------------------------------------------------------- +// Custom STIX identifier +// ----------------------------------------------------------------------------- +// Fork of ADM's stixIdentifierSchema that accepts non-official STIX type +// prefixes. The ADM schema only accepts official STIX types (e.g., +// 'attack-pattern', 'identity'). This schema accepts any valid type prefix +// (e.g., 'release-track', 'x-custom-type'). + +const customStixIdentifierSchema = z + .string() + .refine((val) => val.includes('--') && val.split('--').length === 2, { + message: "Invalid identifier: must comply with format 'type--UUIDv4'", + }) + .refine( + (val) => { + const [type] = val.split('--'); + return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(type); + }, + { + error: (issue) => ({ + message: `Invalid identifier: '${issue.input.split('--')[0]}' is not a valid type prefix`, + }), + }, + ) + .refine( + (val) => { + const [, uuid] = val.split('--'); + return z.uuid().safeParse(uuid).success; + }, + { + message: 'Invalid identifier: contains invalid UUIDv4 format', + }, + ); + +function createCustomStixIdValidator(expectedType) { + return customStixIdentifierSchema.refine((val) => val.startsWith(`${expectedType}--`), { + message: `Invalid identifier: must start with '${expectedType}--'`, + }); +} + +// Prebuilt schema for release track IDs +const releaseTrackIdSchema = createCustomStixIdValidator('release-track'); + +// ----------------------------------------------------------------------------- +// Track name +// ----------------------------------------------------------------------------- + +const trackNameSchema = z + .string() + .min(1, { message: 'Release track name must not be empty' }) + .regex(/^[a-zA-Z0-9 ]+$/, { + message: 'Release track name may only contain alphanumeric characters and spaces', + }); + +// ----------------------------------------------------------------------------- +// Cron expression +// See: https://github.com/colinhacks/zod/issues/4239#issuecomment-3161393771 +// ----------------------------------------------------------------------------- + +const wildcardSchema = z.literal('*'); + +const stepValueSchema = z.enum(Array.from({ length: 9_999 }, (_, i) => String(i + 1))); + +function createCronFieldSchema(min, max) { + const integerSchema = z + .enum(Array.from({ length: max - min + 1 }, (_, i) => String(min + i))) + .transform(Number); + + const rangeSchema = z.templateLiteral([z.int(), z.literal('-'), z.int()]).refine((value) => { + const [start, end] = value.split('-'); + const startResult = integerSchema.safeParse(start); + const endResult = integerSchema.safeParse(end); + return startResult.success && endResult.success && startResult.data <= endResult.data; + }); + + const wildcardOrRangeSchema = wildcardSchema.or(rangeSchema); + + const stepSchema = z + .templateLiteral([wildcardOrRangeSchema, z.literal('/'), z.int()]) + .refine((value) => { + const [base, step] = value.split('/'); + return ( + wildcardOrRangeSchema.safeParse(base).success && stepValueSchema.safeParse(step).success + ); + }); + + const fieldSchema = z.string().refine((value) => { + return value + .split(',') + .every( + (part) => wildcardOrRangeSchema.or(integerSchema).or(stepSchema).safeParse(part).success, + ); + }); + + return fieldSchema; +} + +const minuteSchema = createCronFieldSchema(0, 59); +const hourSchema = createCronFieldSchema(0, 23); +const dayOfMonthSchema = createCronFieldSchema(1, 31); +const monthSchema = createCronFieldSchema(1, 12); +const dayOfWeekSchema = createCronFieldSchema(0, 6); + +const cronSchema = z + .string() + .transform((value) => value.trim().split(/\s+/)) + .refine((fields) => fields.length === 5, { + message: 'Invalid cron expression: expected 5 fields', + }) + .refine((fields) => minuteSchema.safeParse(fields[0]).success, { + message: 'Invalid cron expression: invalid minute field', + }) + .refine((fields) => hourSchema.safeParse(fields[1]).success, { + message: 'Invalid cron expression: invalid hour field', + }) + .refine((fields) => dayOfMonthSchema.safeParse(fields[2]).success, { + message: 'Invalid cron expression: invalid day of month field', + }) + .refine((fields) => monthSchema.safeParse(fields[3]).success, { + message: 'Invalid cron expression: invalid month field', + }) + .refine((fields) => dayOfWeekSchema.safeParse(fields[4]).success, { + message: 'Invalid cron expression: invalid day of week field', + }) + .transform((fields) => fields.join(' ')); + +// ============================================================================= +// Exports +// ============================================================================= + +module.exports = { + // Custom STIX identifiers (extends ADM for non-official type prefixes) + customStixIdentifierSchema, + createCustomStixIdValidator, + releaseTrackIdSchema, + + // Domain schemas + trackNameSchema, + cronSchema, + + // Re-exports from @mitre-attack/attack-data-model + stixIdentifierSchema, + xMitreVersionSchema, + createStixIdValidator, +}; diff --git a/app/lib/release-tracks/release-track-validators.js b/app/lib/release-tracks/release-track-validators.js new file mode 100644 index 00000000..3890a22d --- /dev/null +++ b/app/lib/release-tracks/release-track-validators.js @@ -0,0 +1,79 @@ +'use strict'; + +// ============================================================================= +// Mongoose custom validators for release track model schemas. +// +// Each export is a { validator, message } object compatible with Mongoose's +// custom validator interface. Internally they delegate to the Zod schemas +// defined in ./release-track-schemas.js. +// +// See: https://mongoosejs.com/docs/validation.html#custom-validators +// ============================================================================= + +const { + releaseTrackIdSchema, + trackNameSchema, + cronSchema, + stixIdentifierSchema, + xMitreVersionSchema, + createStixIdValidator, +} = require('./release-track-schemas'); + +// ----------------------------------------------------------------------------- +// Mongoose validators +// ----------------------------------------------------------------------------- + +const validateTrackId = { + validator: (v) => releaseTrackIdSchema.safeParse(v).success, + message: (props) => + `"${props.value}" is not a valid release track ID (expected "release-track--")`, +}; + +const validateTrackName = { + validator: (v) => trackNameSchema.safeParse(v).success, + message: (props) => + `"${props.value}" is not a valid release track name (only alphanumeric characters and spaces allowed)`, +}; + +const validateStixId = { + validator: (v) => stixIdentifierSchema.safeParse(v).success, + message: (props) => `"${props.value}" is not a valid STIX ID (expected "--")`, +}; + +const validateIdentityRef = { + validator: (v) => createStixIdValidator('identity').safeParse(v).success, + message: (props) => + `"${props.value}" is not a valid identity reference (expected "identity--")`, +}; + +const validateMarkingDefRefs = { + validator: (v) => + v.every((ref) => createStixIdValidator('marking-definition').safeParse(ref).success), + message: () => + 'Each marking reference must be a valid marking-definition ID (expected "marking-definition--")', +}; + +const validateVersion = { + validator: (v) => v === null || xMitreVersionSchema.safeParse(v).success, + message: (props) => + `"${props.value}" is not a valid version (expected MAJOR.MINOR format, e.g. "1.0")`, +}; + +const validateCron = { + validator: (v) => cronSchema.safeParse(v).success, + message: (props) => `"${props.value}" is not a valid cron expression (expected 5 fields)`, +}; + +// ============================================================================= +// Exports +// ============================================================================= + +module.exports = { + validateTrackId, + validateTrackName, + validateStixId, + validateIdentityRef, + validateMarkingDefRefs, + validateVersion, + validateCron, +}; diff --git a/app/models/release-tracks/release-track-snapshot-schema.js b/app/models/release-tracks/release-track-snapshot-schema.js index f1f63f54..5b6d3fb9 100644 --- a/app/models/release-tracks/release-track-snapshot-schema.js +++ b/app/models/release-tracks/release-track-snapshot-schema.js @@ -1,40 +1,14 @@ 'use strict'; const mongoose = require('mongoose'); -const { z } = require('zod'); const { - stixIdentifierSchema, - xMitreVersionSchema, - createStixIdValidator, -} = require('@mitre-attack/attack-data-model'); - -// ============================================================================= -// Custom Mongoose validators -// For context, read: https://mongoosejs.com/docs/validation.html#custom-validators -// ============================================================================= - -// We can't use ADM to validate `track_id` because the ADM's `stixIdentifierSchema` and `createStixIdValidator` -// only supports official STIX types. -// - 'attack-pattern--$uuid' will work -// - 'release-track--$uuid' will NOT work -// TODO we should consider adding a `customStixIdValidator` schema + factoryfunction for checking IDs that follow the STIX ID format but have custom STIX types, e.g., "foobar--$uuid" -const TRACK_ID_RE = /^release-track--[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; -const validateTrackId = { - validator: (v) => TRACK_ID_RE.test(v), - message: (props) => - `"${props.value}" is not a valid release track ID (expected "release-track--")`, -}; - -const validateStixId = { - validator: (v) => stixIdentifierSchema.safeParse(v).success, - message: (props) => `"${props.data}" is not a valid STIX ID (expected "--")`, -}; - -const validateVersion = { - validator: (v) => v === null || xMitreVersionSchema.safeParse(v).success, - message: (props) => - `"${props.data}" is not a valid version (expected MAJOR.MINOR format, e.g. "1.0")`, -}; + validateTrackId, + validateTrackName, + validateStixId, + validateIdentityRef, + validateMarkingDefRefs, + validateVersion, +} = require('../../lib/release-tracks/release-track-validators'); // ============================================================================= // Sub-schemas (all use _id: false to match codebase conventions) @@ -297,31 +271,18 @@ const releaseTrackSnapshotDefinition = { name: { type: String, required: [true, 'Release track name is required'], - validate: { - validator: (v) => z.string().safeParse(v).success, - message: (props) => - `"${props.value}" is not a valid release track name (only alphanumeric characters and spaces allowed)`, - }, + validate: validateTrackName, }, description: { type: String }, created: { type: Date, required: true }, created_by_ref: { type: String, - validate: { - validator: (v) => createStixIdValidator('identity').safeParse(v).success, - message: (props) => - `"${props.data}" is not a valid identity reference (expected "identity--")`, - }, + validate: validateIdentityRef, }, object_marking_refs: { type: [String], default: undefined, - validate: { - validator: (v) => - v.every((ref) => createStixIdValidator('marking-definition').safeParse(ref).success), - message: () => - 'Each marking reference must be a valid marking-definition ID (expected "marking-definition--")', - }, + validate: validateMarkingDefRefs, }, // --- Standard track tiers --- From 7e2054f845b0adc9a3bdf3e240716d1c22c9533f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:04:52 -0500 Subject: [PATCH 182/370] refactor(release-track-registry-model): simplify model validation middleware --- .../release-track-registry-model.js | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/app/models/release-tracks/release-track-registry-model.js b/app/models/release-tracks/release-track-registry-model.js index 0aa5c8fd..3bf2ef4b 100644 --- a/app/models/release-tracks/release-track-registry-model.js +++ b/app/models/release-tracks/release-track-registry-model.js @@ -1,13 +1,12 @@ 'use strict'; const mongoose = require('mongoose'); - -// --- Validation patterns --- - -const TRACK_ID_RE = /^release-track--[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; -const TRACK_NAME_RE = /^[a-zA-Z0-9 ]+$/; -const VERSION_RE = /^\d+\.\d+$/; -const CRON_RE = /^(\S+\s+){4}\S+$/; +const { + validateTrackId, + validateTrackName, + validateVersion, + validateCron, +} = require('../../lib/release-tracks/release-track-validators'); // --- Sub-schemas --- @@ -19,10 +18,7 @@ const snapshotScheduleDefinition = { }, cron: { type: String, - validate: { - validator: (v) => CRON_RE.test(v), - message: (props) => `"${props.value}" is not a valid cron expression (expected 5 fields)`, - }, + validate: validateCron, }, dates: { type: [Date], default: undefined }, }; @@ -35,11 +31,7 @@ const releaseTrackRegistryDefinition = { type: String, required: [true, 'Release track ID is required'], index: { unique: true }, - validate: { - validator: (v) => TRACK_ID_RE.test(v), - message: (props) => - `"${props.value}" is not a valid release track ID (expected "release-track--")`, - }, + validate: validateTrackId, }, type: { type: String, @@ -49,11 +41,7 @@ const releaseTrackRegistryDefinition = { name: { type: String, required: [true, 'Release track name is required'], - validate: { - validator: (v) => TRACK_NAME_RE.test(v), - message: (props) => - `"${props.value}" is not a valid release track name (only alphanumeric characters and spaces allowed)`, - }, + validate: validateTrackName, }, description: { type: String }, @@ -62,11 +50,7 @@ const releaseTrackRegistryDefinition = { latest_tagged_version: { type: String, default: null, - validate: { - validator: (v) => v === null || VERSION_RE.test(v), - message: (props) => - `"${props.value}" is not a valid version (expected MAJOR.MINOR format, e.g. "1.0")`, - }, + validate: validateVersion, }, snapshot_count: { type: Number, default: 0 }, tagged_release_count: { type: Number, default: 0 }, From 1da83d6b7dce840dec0c4e45f6677ef822ee8e8b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:14:10 -0500 Subject: [PATCH 183/370] feat(release-tracks): implement dynamic dao/repository for registry and snapshots --- .../release-track-dynamic.repository.js | 218 ++++++++++++++++++ .../release-track-registry.repository.js | 119 ++++++++++ 2 files changed, 337 insertions(+) create mode 100644 app/repository/release-tracks/release-track-dynamic.repository.js create mode 100644 app/repository/release-tracks/release-track-registry.repository.js diff --git a/app/repository/release-tracks/release-track-dynamic.repository.js b/app/repository/release-tracks/release-track-dynamic.repository.js new file mode 100644 index 00000000..8457c079 --- /dev/null +++ b/app/repository/release-tracks/release-track-dynamic.repository.js @@ -0,0 +1,218 @@ +'use strict'; + +const modelFactory = require('../../models/release-tracks/model-factory'); +const { + DatabaseError, + DuplicateIdError, + BadlyFormattedParameterError, +} = require('../../exceptions'); +const logger = require('../../lib/logger'); + +class ReleaseTrackDynamicRepository { + constructor(factory) { + this.modelFactory = factory; + } + + /** + * Resolve the Mongoose model for a given track. + * @param {string} trackId + * @returns {import('mongoose').Model} + */ + _getModel(trackId) { + return this.modelFactory.getModel(trackId); + } + + async getLatestSnapshot(trackId) { + try { + const Model = this._getModel(trackId); + return await Model.findOne({ id: trackId }).sort({ modified: -1 }).lean().exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async getSnapshotByModified(trackId, modified) { + try { + const Model = this._getModel(trackId); + return await Model.findOne({ id: trackId, modified }).lean().exec(); + } catch (err) { + if (err.name === 'CastError') { + throw new BadlyFormattedParameterError({ parameterName: 'modified' }); + } + throw new DatabaseError(err); + } + } + + async getLatestTaggedSnapshot(trackId) { + try { + const Model = this._getModel(trackId); + return await Model.findOne({ + id: trackId, + version: { $ne: null }, + }) + .sort({ modified: -1 }) + .lean() + .exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async getSnapshotByVersion(trackId, version) { + try { + const Model = this._getModel(trackId); + return await Model.findOne({ id: trackId, version }).lean().exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async getAllSnapshots(trackId, options = {}) { + try { + const Model = this._getModel(trackId); + const query = { id: trackId }; + + if (options.taggedOnly) { + query.version = { $ne: null }; + } + + let findQuery = Model.find(query); + + if (options.projection) { + findQuery = findQuery.select(options.projection); + } + + findQuery = findQuery.sort({ modified: -1 }); + + const totalCount = await Model.countDocuments(query).exec(); + + findQuery = findQuery.skip(options.offset || 0); + if (options.limit) { + findQuery = findQuery.limit(options.limit); + } + + const documents = await findQuery.lean().exec(); + + return { + data: documents, + pagination: { + total: totalCount, + offset: options.offset || 0, + limit: options.limit || 0, + }, + }; + } catch (err) { + throw new DatabaseError(err); + } + } + + async saveSnapshot(trackId, snapshotData) { + try { + const Model = this._getModel(trackId); + const document = new Model(snapshotData); + const saved = await document.save(); + return saved.toObject(); + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new DuplicateIdError({ + details: `Snapshot with modified '${snapshotData.modified}' already exists for track '${trackId}'.`, + }); + } + throw new DatabaseError(err); + } + } + + async tagSnapshotInPlace(trackId, modified, versionData) { + try { + const Model = this._getModel(trackId); + const result = await Model.findOneAndUpdate( + { + id: trackId, + modified: modified, + version: null, // Guard: only tag untagged snapshots + }, + { + $set: { version: versionData.version }, + $push: { version_history: versionData.versionHistoryEntry }, + }, + { + new: true, + runValidators: true, + lean: true, + }, + ).exec(); + + return result; + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new DuplicateIdError({ + details: `Version conflict while tagging snapshot for track '${trackId}'.`, + }); + } + throw new DatabaseError(err); + } + } + + async updateSnapshot(trackId, modified, updateOps) { + try { + const Model = this._getModel(trackId); + const result = await Model.findOneAndUpdate({ id: trackId, modified }, updateOps, { + new: true, + runValidators: true, + }).exec(); + + return result; + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new DuplicateIdError({ + details: `Duplicate key conflict while updating snapshot for track '${trackId}'.`, + }); + } + throw new DatabaseError(err); + } + } + + async deleteSnapshot(trackId, modified) { + try { + const Model = this._getModel(trackId); + return await Model.findOneAndDelete({ id: trackId, modified }).lean().exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async deleteAllSnapshots(trackId) { + try { + const Model = this._getModel(trackId); + const result = await Model.deleteMany({ id: trackId }).exec(); + logger.verbose( + `DynamicRepository: Deleted ${result.deletedCount} snapshots for track "${trackId}"`, + ); + return result; + } catch (err) { + throw new DatabaseError(err); + } + } + + async dropCollection(trackId) { + try { + const Model = this._getModel(trackId); + await Model.collection.drop(); + logger.verbose(`DynamicRepository: Dropped collection for track "${trackId}"`); + } catch (err) { + // MongoDB throws "ns not found" if the collection doesn't exist -- safe to ignore + if (err.message && err.message.includes('ns not found')) { + logger.verbose( + `DynamicRepository: Collection for track "${trackId}" did not exist, skipping drop`, + ); + } else { + throw new DatabaseError(err); + } + } finally { + // Always clean up the cached model, even if drop failed or collection didn't exist + this.modelFactory.removeModel(trackId); + } + } +} + +module.exports = new ReleaseTrackDynamicRepository(modelFactory); diff --git a/app/repository/release-tracks/release-track-registry.repository.js b/app/repository/release-tracks/release-track-registry.repository.js new file mode 100644 index 00000000..de21751e --- /dev/null +++ b/app/repository/release-tracks/release-track-registry.repository.js @@ -0,0 +1,119 @@ +'use strict'; + +const ReleaseTrackRegistryModel = require('../../models/release-tracks/release-track-registry-model'); +const regexValidator = require('../../lib/regex'); +const { + DatabaseError, + DuplicateIdError, + BadlyFormattedParameterError, +} = require('../../exceptions'); + +class ReleaseTrackRegistryRepository { + constructor(model) { + this.model = model; + } + + async create(data) { + try { + const document = new this.model(data); + const saved = await document.save(); + return saved.toObject(); + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new DuplicateIdError({ + details: `Release track with id '${data.track_id}' already exists.`, + }); + } + throw new DatabaseError(err); + } + } + + async findByTrackId(trackId) { + try { + return await this.model.findOne({ track_id: trackId }).lean().exec(); + } catch (err) { + if (err.name === 'CastError') { + throw new BadlyFormattedParameterError({ parameterName: 'trackId' }); + } + throw new DatabaseError(err); + } + } + + async findAll(options = {}) { + try { + const query = {}; + + if (options.type) { + query.type = options.type; + } + + const aggregation = [{ $sort: { name: 1 } }, { $match: query }]; + + if (options.search) { + const sanitized = regexValidator.sanitizeRegex(options.search); + aggregation.push({ + $match: { + $or: [ + { name: { $regex: sanitized, $options: 'i' } }, + { description: { $regex: sanitized, $options: 'i' } }, + ], + }, + }); + } + + // Total count before pagination + const totalCountResult = await this.model.aggregate(aggregation).count('totalCount').exec(); + const totalCount = totalCountResult[0]?.totalCount || 0; + + // Pagination + aggregation.push({ $skip: options.offset || 0 }); + if (options.limit) { + aggregation.push({ $limit: options.limit }); + } + + const documents = await this.model.aggregate(aggregation).exec(); + + return { + data: documents, + pagination: { + total: totalCount, + offset: options.offset || 0, + limit: options.limit || 0, + }, + }; + } catch (err) { + throw new DatabaseError(err); + } + } + + async updateByTrackId(trackId, updates) { + try { + const result = await this.model + .findOneAndUpdate( + { track_id: trackId }, + { $set: updates }, + { new: true, runValidators: true, lean: true }, + ) + .exec(); + + return result; + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new DuplicateIdError({ + details: `Duplicate key conflict while updating track '${trackId}'.`, + }); + } + throw new DatabaseError(err); + } + } + + async deleteByTrackId(trackId) { + try { + return await this.model.findOneAndDelete({ track_id: trackId }).lean().exec(); + } catch (err) { + throw new DatabaseError(err); + } + } +} + +module.exports = new ReleaseTrackRegistryRepository(ReleaseTrackRegistryModel); From 67eaca5ed4c2775d70498aa656ed6698ee7f1823 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:51:12 -0500 Subject: [PATCH 184/370] feat(release-tracks): implement service facade, controller, and routes - add 4 new exception classes to app/exceptions/index.js - register new exceptions in app/lib/error-handler.js - add req body zod schemas to release-track-schemas.js - create service facade (will implement later) - create controller - create routes --- app/controllers/release-tracks-controller.js | 775 ++++++++++++++++++ app/exceptions/index.js | 49 ++ app/lib/error-handler.js | 18 +- .../release-tracks/release-track-schemas.js | 228 ++++++ app/routes/release-tracks-routes.js | 300 +++++++ .../release-tracks/release-tracks-service.js | 199 +++++ 6 files changed, 1566 insertions(+), 3 deletions(-) create mode 100644 app/controllers/release-tracks-controller.js create mode 100644 app/routes/release-tracks-routes.js create mode 100644 app/services/release-tracks/release-tracks-service.js diff --git a/app/controllers/release-tracks-controller.js b/app/controllers/release-tracks-controller.js new file mode 100644 index 00000000..dd111202 --- /dev/null +++ b/app/controllers/release-tracks-controller.js @@ -0,0 +1,775 @@ +'use strict'; + +// ============================================================================= +// Release Tracks Controller +// +// Request parsing, Zod validation, and delegation to the service facade. +// Each handler follows the pattern established in collections-controller-v2.js: +// 1. Validate path params / query params / body with Zod safeParse +// 2. On failure, forward a typed error via next() +// 3. Build options, delegate to service facade +// 4. Return appropriate HTTP status +// 5. Forward unexpected errors to centralized error handler via next() +// ============================================================================= + +const releaseTracksService = require('../services/release-tracks/release-tracks-service'); +const logger = require('../lib/logger'); +const { + InvalidQueryStringParameterError, + BadRequestError, + NotImplementedError, +} = require('../exceptions'); +const { + domainParamSchema, + formatQuerySchema, + includeQuerySchema, + trackTypeQuerySchema, + workflowStatusSchema, + createTrackBodySchema, + createFromBundleBodySchema, + updateMetadataBodySchema, + updateContentsBodySchema, + bumpBodySchema, + cloneBodySchema, + addCandidatesBodySchema, + reviewCandidatesBodySchema, + promoteCandidatesBodySchema, + demoteStagedBodySchema, + updateCandidateVersionBodySchema, + updateConfigBodySchema, + updateCompositionBodySchema, + createVirtualSnapshotBodySchema, + xMitreVersionSchema, +} = require('../lib/release-tracks/release-track-schemas'); + +// ============================================================================= +// Helpers +// ============================================================================= + +/** + * Parse an optional query parameter with a Zod schema, returning the parsed + * value on success or a default value on failure/absence. + */ +function parseOptionalQuery(value, schema, defaultValue) { + if (value === undefined || value === null) return defaultValue; + const result = schema.safeParse(value); + return result.success ? result.data : defaultValue; +} + +/** + * Parse common query parameters shared across GET snapshot endpoints. + */ +function parseSnapshotQueryParams(query) { + return { + format: parseOptionalQuery(query.format, formatQuerySchema, 'bundle'), + include: parseOptionalQuery(query.include, includeQuerySchema, undefined), + releases: query.releases === 'only' ? 'only' : undefined, + version: parseOptionalQuery(query.version, xMitreVersionSchema, undefined), + versions: query.versions === 'all' ? 'all' : undefined, + limit: query.limit ? parseInt(query.limit, 10) : undefined, + offset: query.offset ? parseInt(query.offset, 10) : undefined, + }; +} + +// ============================================================================= +// Ephemeral +// ============================================================================= + +/** GET /api/release-tracks/ephemeral/:domain */ +exports.retrieveEphemeralByDomain = async function retrieveEphemeralByDomain(req, res, next) { + try { + const domainResult = domainParamSchema.safeParse(req.params.domain); + if (!domainResult.success) { + return next( + new InvalidQueryStringParameterError({ + parameterName: 'domain', + message: 'Invalid domain parameter. Must be one of: enterprise, ics, mobile', + }), + ); + } + + const format = parseOptionalQuery(req.query.format, formatQuerySchema, 'bundle'); + const result = await releaseTracksService.getEphemeralBundle(domainResult.data, format); + logger.debug(`Success: Retrieved ephemeral ${domainResult.data} bundle`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to retrieve ephemeral bundle: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Track management +// ============================================================================= + +/** GET /api/release-tracks */ +exports.listReleaseTracks = async function listReleaseTracks(req, res, next) { + try { + const options = { + type: parseOptionalQuery(req.query.type, trackTypeQuerySchema, undefined), + releases: req.query.releases === 'only' ? 'only' : undefined, + limit: req.query.limit ? parseInt(req.query.limit, 10) : undefined, + offset: req.query.offset ? parseInt(req.query.offset, 10) : undefined, + search: req.query.search || undefined, + }; + + const result = await releaseTracksService.listTracks(options); + logger.debug('Success: Retrieved release tracks list'); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to list release tracks: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/new */ +exports.createReleaseTrack = async function createReleaseTrack(req, res, next) { + try { + const bodyResult = createTrackBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid request body', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.createTrack({ + ...bodyResult.data, + userAccountId: req.user?.userAccountId, + }); + logger.debug(`Success: Created release track "${bodyResult.data.name}"`); + return res.status(201).send(result); + } catch (err) { + logger.error('Failed to create release track: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/new-from-bundle */ +exports.createReleaseTrackFromBundle = async function createReleaseTrackFromBundle(req, res, next) { + try { + const bodyResult = createFromBundleBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid STIX bundle', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.createTrackFromBundle(bodyResult.data); + logger.debug('Success: Created release track from bundle'); + return res.status(201).send(result); + } catch (err) { + logger.error('Failed to create release track from bundle: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/import */ +exports.importReleaseTrack = async function importReleaseTrack(_req, _res, next) { + return next( + new NotImplementedError('release-tracks-controller', 'importReleaseTrack', { + message: 'Release track import is not yet implemented', + }), + ); +}; + +/** GET /api/release-tracks/:id */ +exports.retrieveLatestSnapshot = async function retrieveLatestSnapshot(req, res, next) { + try { + const queryOptions = parseSnapshotQueryParams(req.query); + + const result = await releaseTracksService.getLatestSnapshot(req.params.id, queryOptions); + logger.debug(`Success: Retrieved latest snapshot for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to retrieve latest snapshot: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/meta */ +exports.updateMetadataByLatest = async function updateMetadataByLatest(req, res, next) { + try { + const bodyResult = updateMetadataBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid metadata update', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateMetadata( + req.params.id, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Updated metadata for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update track metadata: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/contents */ +exports.updateContentsByLatest = async function updateContentsByLatest(req, res, next) { + try { + const bodyResult = updateContentsBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid contents update', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateContents( + req.params.id, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Updated contents for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update track contents: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/bump */ +exports.bumpByLatest = async function bumpByLatest(req, res, next) { + try { + const bodyResult = bumpBodySchema.safeParse(req.body || {}); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid bump request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.bumpLatest(req.params.id, { + ...bodyResult.data, + userAccountId: req.user?.userAccountId, + }); + logger.debug(`Success: Bumped version for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to bump track version: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/clone */ +exports.cloneByLatest = async function cloneByLatest(req, res, next) { + try { + const bodyResult = cloneBodySchema.safeParse(req.body || {}); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid clone request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.cloneTrack(req.params.id, { + ...(bodyResult.data || {}), + userAccountId: req.user?.userAccountId, + }); + logger.debug(`Success: Cloned track ${req.params.id}`); + return res.status(201).send(result); + } catch (err) { + logger.error('Failed to clone track: ' + err); + return next(err); + } +}; + +/** DELETE /api/release-tracks/:id */ +exports.deleteReleaseTrack = async function deleteReleaseTrack(req, res, next) { + try { + await releaseTracksService.deleteTrack(req.params.id); + logger.debug(`Success: Deleted track ${req.params.id}`); + return res.status(204).end(); + } catch (err) { + logger.error('Failed to delete track: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Snapshot-specific operations +// ============================================================================= + +/** GET /api/release-tracks/:id/snapshots/:modified */ +exports.retrieveSnapshotByModified = async function retrieveSnapshotByModified(req, res, next) { + try { + const queryOptions = parseSnapshotQueryParams(req.query); + + const result = await releaseTracksService.getSnapshotByModified( + req.params.id, + req.params.modified, + queryOptions, + ); + logger.debug(`Success: Retrieved snapshot ${req.params.modified} for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to retrieve snapshot by modified: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/snapshots/:modified/meta */ +exports.updateMetadataByModified = async function updateMetadataByModified(req, res, next) { + try { + const bodyResult = updateMetadataBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid metadata update', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateMetadataByModified( + req.params.id, + req.params.modified, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Updated metadata for snapshot ${req.params.modified}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update snapshot metadata: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/snapshots/:modified/contents */ +exports.updateContentsByModified = async function updateContentsByModified(req, res, next) { + try { + const bodyResult = updateContentsBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid contents update', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateContentsByModified( + req.params.id, + req.params.modified, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Updated contents for snapshot ${req.params.modified}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update snapshot contents: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/snapshots/:modified/bump */ +exports.bumpByModified = async function bumpByModified(req, res, next) { + try { + const bodyResult = bumpBodySchema.safeParse(req.body || {}); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid bump request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.bumpByModified(req.params.id, req.params.modified, { + ...bodyResult.data, + userAccountId: req.user?.userAccountId, + }); + logger.debug(`Success: Bumped version for snapshot ${req.params.modified}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to bump snapshot version: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/snapshots/:modified/clone */ +exports.cloneByModified = async function cloneByModified(req, res, next) { + try { + const bodyResult = cloneBodySchema.safeParse(req.body || {}); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid clone request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.cloneFromSnapshot( + req.params.id, + req.params.modified, + { + ...(bodyResult.data || {}), + userAccountId: req.user?.userAccountId, + }, + ); + logger.debug(`Success: Cloned from snapshot ${req.params.modified}`); + return res.status(201).send(result); + } catch (err) { + logger.error('Failed to clone from snapshot: ' + err); + return next(err); + } +}; + +/** DELETE /api/release-tracks/:id/snapshots/:modified */ +exports.deleteSnapshotByModified = async function deleteSnapshotByModified(req, res, next) { + try { + await releaseTracksService.deleteSnapshot(req.params.id, req.params.modified); + logger.debug(`Success: Deleted snapshot ${req.params.modified} from track ${req.params.id}`); + return res.status(204).end(); + } catch (err) { + logger.error('Failed to delete snapshot: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Candidate management +// ============================================================================= + +/** POST /api/release-tracks/:id/candidates */ +exports.addCandidates = async function addCandidates(req, res, next) { + try { + const bodyResult = addCandidatesBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid candidates request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.addCandidates( + req.params.id, + bodyResult.data.object_refs, + req.user?.userAccountId, + ); + logger.debug(`Success: Added candidates to track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to add candidates: ' + err); + return next(err); + } +}; + +/** GET /api/release-tracks/:id/candidates */ +exports.listCandidates = async function listCandidates(req, res, next) { + try { + const options = { + status: parseOptionalQuery(req.query.status, workflowStatusSchema, undefined), + limit: req.query.limit ? parseInt(req.query.limit, 10) : undefined, + offset: req.query.offset ? parseInt(req.query.offset, 10) : undefined, + }; + + const result = await releaseTracksService.listCandidates(req.params.id, options); + logger.debug(`Success: Listed candidates for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to list candidates: ' + err); + return next(err); + } +}; + +/** DELETE /api/release-tracks/:id/candidates/:objectRef */ +exports.removeCandidate = async function removeCandidate(req, res, next) { + try { + await releaseTracksService.removeCandidate(req.params.id, req.params.objectRef); + logger.debug(`Success: Removed candidate ${req.params.objectRef} from track ${req.params.id}`); + return res.status(204).end(); + } catch (err) { + logger.error('Failed to remove candidate: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/candidates/review */ +exports.reviewCandidates = async function reviewCandidates(req, res, next) { + try { + const bodyResult = reviewCandidatesBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid review request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.reviewCandidates( + req.params.id, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Reviewed candidates for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to review candidates: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/candidates/promote */ +exports.promoteCandidates = async function promoteCandidates(req, res, next) { + try { + const bodyResult = promoteCandidatesBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid promote request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.promoteCandidates( + req.params.id, + bodyResult.data.object_refs, + req.user?.userAccountId, + ); + logger.debug(`Success: Promoted candidates for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to promote candidates: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/candidates/:objectRef/update-version */ +exports.updateCandidateVersion = async function updateCandidateVersion(req, res, next) { + try { + const bodyResult = updateCandidateVersionBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid version update request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateCandidateVersion( + req.params.id, + req.params.objectRef, + bodyResult.data, + ); + logger.debug(`Success: Updated version for candidate ${req.params.objectRef}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update candidate version: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Staged objects +// ============================================================================= + +/** GET /api/release-tracks/:id/staged */ +exports.listStaged = async function listStaged(req, res, next) { + try { + const result = await releaseTracksService.listStaged(req.params.id); + logger.debug(`Success: Listed staged objects for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to list staged objects: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/staged/demote */ +exports.demoteStaged = async function demoteStaged(req, res, next) { + try { + const bodyResult = demoteStagedBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid demote request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.demoteStaged( + req.params.id, + bodyResult.data.object_refs, + req.user?.userAccountId, + ); + logger.debug(`Success: Demoted staged objects for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to demote staged objects: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Configuration +// ============================================================================= + +/** GET /api/release-tracks/:id/config */ +exports.getConfig = async function getConfig(req, res, next) { + try { + const result = await releaseTracksService.getConfig(req.params.id); + logger.debug(`Success: Retrieved config for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to get track config: ' + err); + return next(err); + } +}; + +/** PUT /api/release-tracks/:id/config */ +exports.updateConfig = async function updateConfig(req, res, next) { + try { + const bodyResult = updateConfigBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid config update', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateConfig( + req.params.id, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Updated config for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update track config: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Preview & dry run +// ============================================================================= + +/** GET /api/release-tracks/:id/bump/preview */ +exports.previewBump = async function previewBump(req, res, next) { + try { + const format = parseOptionalQuery(req.query.format, formatQuerySchema, 'workbench'); + + const result = await releaseTracksService.previewBump(req.params.id, format); + logger.debug(`Success: Generated bump preview for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to preview bump: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Object versions +// ============================================================================= + +/** GET /api/release-tracks/:id/objects/:objectRef/versions */ +exports.listObjectVersions = async function listObjectVersions(req, res, next) { + try { + const result = await releaseTracksService.listObjectVersions( + req.params.id, + req.params.objectRef, + ); + logger.debug(`Success: Listed versions for object ${req.params.objectRef}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to list object versions: ' + err); + return next(err); + } +}; + +// ============================================================================= +// Virtual track operations +// ============================================================================= + +/** PUT /api/release-tracks/:id/composition */ +exports.updateComposition = async function updateComposition(req, res, next) { + try { + const bodyResult = updateCompositionBodySchema.safeParse(req.body); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid composition update', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.updateComposition( + req.params.id, + bodyResult.data, + req.user?.userAccountId, + ); + logger.debug(`Success: Updated composition for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to update composition: ' + err); + return next(err); + } +}; + +/** POST /api/release-tracks/:id/snapshots/create */ +exports.createVirtualSnapshot = async function createVirtualSnapshot(req, res, next) { + try { + const bodyResult = createVirtualSnapshotBodySchema.safeParse(req.body || {}); + if (!bodyResult.success) { + return next( + new BadRequestError({ + message: 'Invalid virtual snapshot request', + details: bodyResult.error.errors, + }), + ); + } + + const result = await releaseTracksService.createVirtualSnapshot(req.params.id, { + ...(bodyResult.data || {}), + userAccountId: req.user?.userAccountId, + }); + logger.debug(`Success: Created virtual snapshot for track ${req.params.id}`); + return res.status(201).send(result); + } catch (err) { + logger.error('Failed to create virtual snapshot: ' + err); + return next(err); + } +}; + +/** GET /api/release-tracks/:id/snapshots/preview */ +exports.previewVirtualSnapshot = async function previewVirtualSnapshot(req, res, next) { + try { + const result = await releaseTracksService.previewVirtualSnapshot(req.params.id); + logger.debug(`Success: Generated virtual snapshot preview for track ${req.params.id}`); + return res.status(200).send(result); + } catch (err) { + logger.error('Failed to preview virtual snapshot: ' + err); + return next(err); + } +}; diff --git a/app/exceptions/index.js b/app/exceptions/index.js index 9a190b98..9f7eef7f 100644 --- a/app/exceptions/index.js +++ b/app/exceptions/index.js @@ -204,6 +204,45 @@ class SchemaValidationError extends CustomError { } } +class AlreadyReleasedError extends CustomError { + constructor(version, options) { + super(`This snapshot has already been tagged as version ${version}`, options); + } +} + +class InvalidVersionError extends CustomError { + constructor(message, options) { + super(message || 'Invalid version', options); + } +} + +class ReleaseConflictError extends CustomError { + constructor(message, options) { + super(message || 'Release conflict: promotion aborted due to conflicting objects', options); + } +} + +class NoTaggedSnapshotsError extends CustomError { + constructor(trackId, options) { + super(`Component track ${trackId} has no tagged snapshots`, options); + } +} + +class InvalidComponentTypeError extends CustomError { + constructor(trackId, options) { + super( + `Component track ${trackId} must be a standard track (virtual nesting is not allowed)`, + options, + ); + } +} + +class TrackNotFoundError extends CustomError { + constructor(trackId, options) { + super(`Release track ${trackId} not found`, options); + } +} + module.exports = { //** General errors */ NotImplementedError, @@ -221,6 +260,16 @@ module.exports = { ValidationError, SchemaValidationError, + //** Version control errors */ + AlreadyReleasedError, + InvalidVersionError, + + //** Release track errors */ + ReleaseConflictError, + NoTaggedSnapshotsError, + InvalidComponentTypeError, + TrackNotFoundError, + //** Database-related errors */ DuplicateIdError, DuplicateEmailError, diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index 09ee5478..2294b1fb 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -30,6 +30,12 @@ const { ImmutablePropertyError, InvalidPostOperationError, DefaultMarkingDefinitionsNotFoundError, + AlreadyReleasedError, + InvalidVersionError, + ReleaseConflictError, + NoTaggedSnapshotsError, + InvalidComponentTypeError, + TrackNotFoundError, } = require('../exceptions'); exports.bodyParser = function (err, req, res, next) { @@ -86,7 +92,10 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof InvalidTypeError || err instanceof PropertyNotAllowedError || err instanceof CannotUpdateStaticObjectError || - err instanceof BadRequestError + err instanceof BadRequestError || + err instanceof InvalidVersionError || + err instanceof NoTaggedSnapshotsError || + err instanceof InvalidComponentTypeError ) { logger.warn(`Bad request: ${err.message}`); return res.status(400).send(buildErrorResponse(err)); @@ -98,7 +107,8 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof SystemConfigurationNotFound || err instanceof OrganizationIdentityNotFoundError || err instanceof AnonymousUserAccountNotFoundError || - err instanceof DefaultMarkingDefinitionsNotFoundError + err instanceof DefaultMarkingDefinitionsNotFoundError || + err instanceof TrackNotFoundError ) { logger.warn(`Not found: ${err.message}`); return res.status(404).send(buildErrorResponse(err)); @@ -108,7 +118,9 @@ exports.serviceExceptions = function (err, req, res, next) { if ( err instanceof DuplicateIdError || err instanceof DuplicateEmailError || - err instanceof DuplicateNameError + err instanceof DuplicateNameError || + err instanceof AlreadyReleasedError || + err instanceof ReleaseConflictError ) { logger.warn(`Conflict: ${err.message}`); return res.status(409).send(buildErrorResponse(err)); diff --git a/app/lib/release-tracks/release-track-schemas.js b/app/lib/release-tracks/release-track-schemas.js index d4c8f931..5580f00a 100644 --- a/app/lib/release-tracks/release-track-schemas.js +++ b/app/lib/release-tracks/release-track-schemas.js @@ -144,6 +144,199 @@ const cronSchema = z }) .transform((fields) => fields.join(' ')); +// ============================================================================= +// Query parameter schemas (used inline by controller handlers) +// ============================================================================= + +const domainParamSchema = z.enum(['enterprise', 'ics', 'mobile']); + +const formatQuerySchema = z.enum(['bundle', 'filesystemstore', 'workbench']); + +const includeQuerySchema = z.enum(['staged', 'candidates', 'all']); + +const trackTypeQuerySchema = z.enum(['standard', 'virtual']); + +const bumpTypeSchema = z.enum(['major', 'minor']); + +const workflowStatusSchema = z.enum(['work-in-progress', 'awaiting-review', 'reviewed']); + +const candidacyThresholdSchema = z.enum(['work-in-progress', 'awaiting-review', 'reviewed']); + +const deduplicationStrategySchema = z.enum([ + 'prioritize_latest_object', + 'prioritize_latest_snapshot', + 'prioritize_higher_priority', + 'quarantine', +]); + +const resolutionStrategySchema = z.enum(['latest_tagged', 'specific_version', 'specific_snapshot']); + +const conflictPolicySchema = z.enum([ + 'prefer_latest', + 'always_overwrite', + 'always_reject', + 'abort', +]); + +// ============================================================================= +// Request body schemas (used inline by controller handlers) +// ============================================================================= + +/** POST /release-tracks/new */ +const snapshotScheduleSchema = z.object({ + mode: z.enum(['manual', 'cron', 'dates']), + cron: cronSchema.optional(), + dates: z.array(z.iso.datetime()).optional(), +}); + +const componentTrackSchema = z.object({ + track_id: releaseTrackIdSchema, + resolution_strategy: resolutionStrategySchema, + priority: z.number().int().min(0).optional(), + version: xMitreVersionSchema.optional(), + snapshot: z.iso.datetime().optional(), + filters: z + .object({ + object_types: z.array(z.string()).optional(), + domains: z.array(z.string()).optional(), + }) + .optional(), +}); + +const compositionSchema = z.object({ + component_tracks: z.array(componentTrackSchema).min(1), + deduplication: z + .object({ + strategy: deduplicationStrategySchema, + }) + .optional(), +}); + +const createTrackBodySchema = z.object({ + name: trackNameSchema, + description: z.string().optional(), + type: trackTypeQuerySchema.default('standard'), + object_marking_refs: z.array(stixIdentifierSchema).optional(), + composition: compositionSchema.optional(), + snapshot_schedule: snapshotScheduleSchema.optional(), +}); + +/** POST /release-tracks/new-from-bundle */ +const createFromBundleBodySchema = z.object({ + type: z.literal('bundle'), + id: stixIdentifierSchema, + objects: z.array(z.looseObject({})).min(1), +}); + +/** POST /release-tracks/:id/meta */ +const updateMetadataBodySchema = z.object({ + name: trackNameSchema.optional(), + description: z.string().optional(), + object_marking_refs: z.array(stixIdentifierSchema).optional(), +}); + +/** POST /release-tracks/:id/contents */ +const updateContentsBodySchema = z.object({ + x_mitre_contents: z + .array( + z.object({ + obj_ref: stixIdentifierSchema, + obj_modified: z.iso.datetime().or(z.literal('latest')), + }), + ) + .min(1), +}); + +/** POST /release-tracks/:id/bump */ +const bumpBodySchema = z.object({ + type: bumpTypeSchema.optional(), + version: xMitreVersionSchema.optional(), + dry_run: z.boolean().optional(), +}); + +/** POST /release-tracks/:id/clone */ +const cloneBodySchema = z + .object({ + name: trackNameSchema.optional(), + }) + .optional(); + +/** POST /release-tracks/:id/candidates */ +const objectRefEntrySchema = z.union([ + stixIdentifierSchema, + z.object({ + id: stixIdentifierSchema, + modified: z.iso.datetime().or(z.literal('latest')).optional(), + }), +]); + +const addCandidatesBodySchema = z.object({ + object_refs: z.array(objectRefEntrySchema).min(1), +}); + +/** POST /release-tracks/:id/candidates/review */ +const reviewCandidatesBodySchema = z.object({ + from: workflowStatusSchema, + to: workflowStatusSchema, + object_refs: z + .array( + z.union([ + stixIdentifierSchema, + z.object({ + id: stixIdentifierSchema, + modified: z.iso.datetime().optional(), + }), + ]), + ) + .optional(), +}); + +/** POST /release-tracks/:id/candidates/promote */ +const promoteCandidatesBodySchema = z.object({ + object_refs: z.array(stixIdentifierSchema).min(1), +}); + +/** POST /release-tracks/:id/staged/demote */ +const demoteStagedBodySchema = z.object({ + object_refs: z + .array( + z.object({ + id: stixIdentifierSchema, + modified: z.iso.datetime(), + }), + ) + .min(1), +}); + +/** POST /release-tracks/:id/candidates/:objectRef/update-version */ +const updateCandidateVersionBodySchema = z.object({ + old_modified: z.iso.datetime(), + new_modified: z.iso.datetime(), +}); + +/** PUT /release-tracks/:id/config */ +const promotionConflictsSchema = z.object({ + candidates_to_staged: conflictPolicySchema.exclude(['abort']).optional(), + staged_to_members: conflictPolicySchema.optional(), +}); + +const updateConfigBodySchema = z.object({ + candidacy_threshold: candidacyThresholdSchema.optional(), + auto_promote: z.boolean().optional(), + include_candidates_in_snapshots: z.boolean().optional(), + promotion_conflicts: promotionConflictsSchema.optional(), +}); + +/** PUT /release-tracks/:id/composition */ +const updateCompositionBodySchema = compositionSchema; + +/** POST /release-tracks/:id/snapshots/create */ +const createVirtualSnapshotBodySchema = z + .object({ + description: z.string().optional(), + }) + .optional(); + // ============================================================================= // Exports // ============================================================================= @@ -162,4 +355,39 @@ module.exports = { stixIdentifierSchema, xMitreVersionSchema, createStixIdValidator, + + // Query parameter schemas + domainParamSchema, + formatQuerySchema, + includeQuerySchema, + trackTypeQuerySchema, + bumpTypeSchema, + workflowStatusSchema, + candidacyThresholdSchema, + deduplicationStrategySchema, + resolutionStrategySchema, + conflictPolicySchema, + + // Request body schemas + createTrackBodySchema, + createFromBundleBodySchema, + updateMetadataBodySchema, + updateContentsBodySchema, + bumpBodySchema, + cloneBodySchema, + addCandidatesBodySchema, + reviewCandidatesBodySchema, + promoteCandidatesBodySchema, + demoteStagedBodySchema, + updateCandidateVersionBodySchema, + updateConfigBodySchema, + updateCompositionBodySchema, + createVirtualSnapshotBodySchema, + + // Reusable sub-schemas + componentTrackSchema, + compositionSchema, + snapshotScheduleSchema, + objectRefEntrySchema, + promotionConflictsSchema, }; diff --git a/app/routes/release-tracks-routes.js b/app/routes/release-tracks-routes.js new file mode 100644 index 00000000..120b804e --- /dev/null +++ b/app/routes/release-tracks-routes.js @@ -0,0 +1,300 @@ +'use strict'; + +const express = require('express'); + +const releaseTracksController = require('../controllers/release-tracks-controller'); +const authn = require('../lib/authn-middleware'); +const authz = require('../lib/authz-middleware'); + +const router = express.Router(); + +// ============================================================================= +// Ephemeral (stateless) bundles +// ============================================================================= + +router + .route('/release-tracks/ephemeral/:domain') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.retrieveEphemeralByDomain, + ); + +// ============================================================================= +// Track management (static paths before :id param) +// ============================================================================= + +router + .route('/release-tracks') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.listReleaseTracks, + ); + +router + .route('/release-tracks/new') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.createReleaseTrack, + ); + +router + .route('/release-tracks/new-from-bundle') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.createReleaseTrackFromBundle, + ); + +router + .route('/release-tracks/import') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.importReleaseTrack, + ); + +// ============================================================================= +// Latest snapshot operations (parameterised by :id) +// ============================================================================= + +/** Bump preview must be registered before :id/bump to avoid param conflict */ +router + .route('/release-tracks/:id/bump/preview') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.previewBump, + ); + +router + .route('/release-tracks/:id/meta') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateMetadataByLatest, + ); + +router + .route('/release-tracks/:id/contents') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateContentsByLatest, + ); + +router + .route('/release-tracks/:id/bump') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.bumpByLatest, + ); + +router + .route('/release-tracks/:id/clone') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.cloneByLatest, + ); + +// ============================================================================= +// Candidate management (static sub-paths before :objectRef param) +// ============================================================================= + +router + .route('/release-tracks/:id/candidates/review') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.reviewCandidates, + ); + +router + .route('/release-tracks/:id/candidates/promote') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.promoteCandidates, + ); + +router + .route('/release-tracks/:id/candidates/:objectRef/update-version') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateCandidateVersion, + ); + +router + .route('/release-tracks/:id/candidates/:objectRef') + .delete( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.removeCandidate, + ); + +router + .route('/release-tracks/:id/candidates') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.listCandidates, + ) + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.addCandidates, + ); + +// ============================================================================= +// Staged objects +// ============================================================================= + +router + .route('/release-tracks/:id/staged/demote') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.demoteStaged, + ); + +router + .route('/release-tracks/:id/staged') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.listStaged, + ); + +// ============================================================================= +// Configuration +// ============================================================================= + +router + .route('/release-tracks/:id/config') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.getConfig, + ) + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateConfig, + ); + +// ============================================================================= +// Object version history +// ============================================================================= + +router + .route('/release-tracks/:id/objects/:objectRef/versions') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.listObjectVersions, + ); + +// ============================================================================= +// Virtual track operations (static snapshot sub-paths before :modified param) +// ============================================================================= + +router + .route('/release-tracks/:id/snapshots/preview') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.previewVirtualSnapshot, + ); + +router + .route('/release-tracks/:id/snapshots/create') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.createVirtualSnapshot, + ); + +// ============================================================================= +// Snapshot-specific operations (parameterised by :modified) +// ============================================================================= + +router + .route('/release-tracks/:id/snapshots/:modified/meta') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateMetadataByModified, + ); + +router + .route('/release-tracks/:id/snapshots/:modified/contents') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateContentsByModified, + ); + +router + .route('/release-tracks/:id/snapshots/:modified/bump') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.bumpByModified, + ); + +router + .route('/release-tracks/:id/snapshots/:modified/clone') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.cloneByModified, + ); + +router + .route('/release-tracks/:id/snapshots/:modified') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.retrieveSnapshotByModified, + ) + .delete( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.deleteSnapshotByModified, + ); + +// ============================================================================= +// Virtual track composition +// ============================================================================= + +router + .route('/release-tracks/:id/composition') + .put( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.updateComposition, + ); + +// ============================================================================= +// Retrieve / delete release track (must be last -- :id is a catch-all param) +// ============================================================================= + +router + .route('/release-tracks/:id') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + releaseTracksController.retrieveLatestSnapshot, + ) + .delete( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + releaseTracksController.deleteReleaseTrack, + ); + +module.exports = router; diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js new file mode 100644 index 00000000..232d6311 --- /dev/null +++ b/app/services/release-tracks/release-tracks-service.js @@ -0,0 +1,199 @@ +/* eslint-disable no-unused-vars */ +// TODO remove the above eslint rule after all functions have been implemented +'use strict'; + +// ============================================================================= +// Release Tracks Service Facade +// +// Orchestrator that delegates to domain-specific sub-services. This is the +// single entry point consumed by the controller layer. +// +// All methods currently throw NotImplementedError. Sub-services (WS3-WS7) +// will provide real implementations progressively. +// ============================================================================= + +const { NotImplementedError } = require('../../exceptions'); + +const MODULE = 'release-tracks-service'; + +function notImplemented(methodName) { + throw new NotImplementedError(MODULE, methodName); +} + +// ----------------------------------------------------------------------------- +// Track management +// ----------------------------------------------------------------------------- + +exports.listTracks = async function listTracks(_options) { + notImplemented('listTracks'); +}; + +exports.createTrack = async function createTrack(_data) { + notImplemented('createTrack'); +}; + +exports.createTrackFromBundle = async function createTrackFromBundle(_bundleData) { + notImplemented('createTrackFromBundle'); +}; + +exports.importTrack = async function importTrack(_data) { + notImplemented('importTrack'); +}; + +exports.getLatestSnapshot = async function getLatestSnapshot(_trackId, _options) { + notImplemented('getLatestSnapshot'); +}; + +exports.getSnapshotByModified = async function getSnapshotByModified( + _trackId, + _modified, + _options, +) { + notImplemented('getSnapshotByModified'); +}; + +exports.updateMetadata = async function updateMetadata(_trackId, _updates, _userId) { + notImplemented('updateMetadata'); +}; + +exports.updateMetadataByModified = async function updateMetadataByModified( + _trackId, + _modified, + _updates, + _userId, +) { + notImplemented('updateMetadataByModified'); +}; + +exports.updateContents = async function updateContents(_trackId, _contents, _userId) { + notImplemented('updateContents'); +}; + +exports.updateContentsByModified = async function updateContentsByModified( + _trackId, + _modified, + _contents, + _userId, +) { + notImplemented('updateContentsByModified'); +}; + +exports.cloneTrack = async function cloneTrack(_trackId, _options) { + notImplemented('cloneTrack'); +}; + +exports.cloneFromSnapshot = async function cloneFromSnapshot(_trackId, _modified, _options) { + notImplemented('cloneFromSnapshot'); +}; + +exports.deleteTrack = async function deleteTrack(_trackId) { + notImplemented('deleteTrack'); +}; + +exports.deleteSnapshot = async function deleteSnapshot(_trackId, _modified) { + notImplemented('deleteSnapshot'); +}; + +// ----------------------------------------------------------------------------- +// Ephemeral +// ----------------------------------------------------------------------------- + +exports.getEphemeralBundle = async function getEphemeralBundle(_domain, _format) { + notImplemented('getEphemeralBundle'); +}; + +// ----------------------------------------------------------------------------- +// Candidates +// ----------------------------------------------------------------------------- + +exports.addCandidates = async function addCandidates(_trackId, _objectRefs, _userId) { + notImplemented('addCandidates'); +}; + +exports.listCandidates = async function listCandidates(_trackId, _options) { + notImplemented('listCandidates'); +}; + +exports.removeCandidate = async function removeCandidate(_trackId, _objectRef) { + notImplemented('removeCandidate'); +}; + +exports.reviewCandidates = async function reviewCandidates(_trackId, _reviewData, _userId) { + notImplemented('reviewCandidates'); +}; + +exports.promoteCandidates = async function promoteCandidates(_trackId, _objectRefs, _userId) { + notImplemented('promoteCandidates'); +}; + +exports.updateCandidateVersion = async function updateCandidateVersion( + _trackId, + _objectRef, + _data, +) { + notImplemented('updateCandidateVersion'); +}; + +// ----------------------------------------------------------------------------- +// Staged +// ----------------------------------------------------------------------------- + +exports.listStaged = async function listStaged(_trackId) { + notImplemented('listStaged'); +}; + +exports.demoteStaged = async function demoteStaged(_trackId, _objectRefs, _userId) { + notImplemented('demoteStaged'); +}; + +// ----------------------------------------------------------------------------- +// Versioning +// ----------------------------------------------------------------------------- + +exports.bumpLatest = async function bumpLatest(_trackId, _options) { + notImplemented('bumpLatest'); +}; + +exports.bumpByModified = async function bumpByModified(_trackId, _modified, _options) { + notImplemented('bumpByModified'); +}; + +exports.previewBump = async function previewBump(_trackId, _format) { + notImplemented('previewBump'); +}; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +exports.getConfig = async function getConfig(_trackId) { + notImplemented('getConfig'); +}; + +exports.updateConfig = async function updateConfig(_trackId, _config, _userId) { + notImplemented('updateConfig'); +}; + +// ----------------------------------------------------------------------------- +// Virtual tracks +// ----------------------------------------------------------------------------- + +exports.updateComposition = async function updateComposition(_trackId, _composition, _userId) { + notImplemented('updateComposition'); +}; + +exports.createVirtualSnapshot = async function createVirtualSnapshot(_trackId, _options) { + notImplemented('createVirtualSnapshot'); +}; + +exports.previewVirtualSnapshot = async function previewVirtualSnapshot(_trackId) { + notImplemented('previewVirtualSnapshot'); +}; + +// ----------------------------------------------------------------------------- +// Object versions +// ----------------------------------------------------------------------------- + +exports.listObjectVersions = async function listObjectVersions(_trackId, _objectRef) { + notImplemented('listObjectVersions'); +}; From 85d6fdbcd999d5485de051a0529880d0d2f16c02 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:51:06 -0500 Subject: [PATCH 185/370] feat(release-tracks): implement standard track service - new file (conflict-resolution.js): handles conflict resolution policies when promoting objects between tiers - new file (object-resolver.js): resolves STIX object refs to concrete modified timestamps (when modified=latest) - new file (standard-track-service): manages the 3 tier workflow for standard tracks (candidates -> staged -> members) updated 9 notImplemented stubs in release-tracks-service --- app/lib/release-tracks/conflict-resolution.js | 83 +++ app/lib/release-tracks/object-resolver.js | 82 +++ .../release-tracks/release-tracks-service.js | 144 +++-- .../release-tracks/snapshot-service.js | 477 +++++++++++++++++ .../release-tracks/standard-track-service.js | 496 ++++++++++++++++++ 5 files changed, 1209 insertions(+), 73 deletions(-) create mode 100644 app/lib/release-tracks/conflict-resolution.js create mode 100644 app/lib/release-tracks/object-resolver.js create mode 100644 app/services/release-tracks/snapshot-service.js create mode 100644 app/services/release-tracks/standard-track-service.js diff --git a/app/lib/release-tracks/conflict-resolution.js b/app/lib/release-tracks/conflict-resolution.js new file mode 100644 index 00000000..35a52b9d --- /dev/null +++ b/app/lib/release-tracks/conflict-resolution.js @@ -0,0 +1,83 @@ +'use strict'; + +// ============================================================================= +// Conflict Resolution +// +// Applies conflict resolution policies when promoting objects between tiers. +// A "conflict" occurs when the incoming entry has the same object_ref as an +// existing entry in the target tier but a different object_modified timestamp. +// +// Policies: +// always_overwrite – Replace the incumbent with the incoming entry +// always_reject – Keep the incumbent; incoming is rejected +// prefer_latest – Keep whichever has the newer object_modified +// abort – Throw ReleaseConflictError on any conflict +// ============================================================================= + +const { ReleaseConflictError } = require('../../exceptions'); + +/** + * Merge incoming entries into an existing tier, applying a conflict policy. + * + * @param {Array} existingTier - Current entries in the target tier + * @param {Array} incomingEntries - Entries being promoted into the tier + * @param {string} policy - One of: 'always_overwrite' | 'always_reject' | 'prefer_latest' | 'abort' + * @returns {{ merged: Array, rejected: Array }} + * @throws {ReleaseConflictError} If policy is 'abort' and a conflict is detected + */ +exports.applyConflictPolicy = function applyConflictPolicy(existingTier, incomingEntries, policy) { + const merged = [...existingTier]; + const rejected = []; + + for (const incoming of incomingEntries) { + const conflictIdx = merged.findIndex((e) => e.object_ref === incoming.object_ref); + + if (conflictIdx === -1) { + // No conflict — simply add the incoming entry + merged.push(incoming); + continue; + } + + const incumbent = merged[conflictIdx]; + + switch (policy) { + case 'always_overwrite': + merged[conflictIdx] = incoming; + break; + + case 'always_reject': + rejected.push(incoming); + break; + + case 'prefer_latest': { + const incomingTime = new Date(incoming.object_modified).getTime(); + const incumbentTime = new Date(incumbent.object_modified).getTime(); + if (incomingTime > incumbentTime) { + merged[conflictIdx] = incoming; + } else { + rejected.push(incoming); + } + break; + } + + case 'abort': + throw new ReleaseConflictError( + `Conflict on ${incoming.object_ref}: abort policy prevents promotion`, + { + conflicts: [ + { + object_ref: incoming.object_ref, + incumbent_version: incumbent.object_modified, + incoming_version: incoming.object_modified, + }, + ], + }, + ); + + default: + throw new Error(`Unknown conflict resolution policy: ${policy}`); + } + } + + return { merged, rejected }; +}; diff --git a/app/lib/release-tracks/object-resolver.js b/app/lib/release-tracks/object-resolver.js new file mode 100644 index 00000000..e8aabe0a --- /dev/null +++ b/app/lib/release-tracks/object-resolver.js @@ -0,0 +1,82 @@ +'use strict'; + +// ============================================================================= +// Object Resolver +// +// Resolves STIX object references to concrete modified timestamps by querying +// the existing STIX service layer. This is used when adding candidates with +// `modified: "latest"` or when modified is omitted. +// +// Follows the same serviceMap pattern as import-bundle.js. +// ============================================================================= + +const types = require('../types'); +const { BadRequestError, NotFoundError } = require('../../exceptions'); + +// --------------------------------------------------------------------------- +// Service map – lazy-loaded to avoid circular dependency issues at startup. +// --------------------------------------------------------------------------- + +let _serviceMap = null; + +function getServiceMap() { + if (_serviceMap) return _serviceMap; + + _serviceMap = { + [types.Technique]: require('../../services/stix/techniques-service'), + [types.Tactic]: require('../../services/stix/tactics-service'), + [types.Group]: require('../../services/stix/groups-service'), + [types.Campaign]: require('../../services/stix/campaigns-service'), + [types.Mitigation]: require('../../services/stix/mitigations-service'), + [types.Matrix]: require('../../services/stix/matrices-service'), + [types.Relationship]: require('../../services/stix/relationships-service'), + [types.MarkingDefinition]: require('../../services/stix/marking-definitions-service'), + [types.Identity]: require('../../services/stix/identities-service'), + [types.Note]: require('../../services/system/notes-service'), + [types.DataSource]: require('../../services/stix/data-sources-service'), + [types.DataComponent]: require('../../services/stix/data-components-service'), + [types.Asset]: require('../../services/stix/assets-service'), + [types.Analytic]: require('../../services/stix/analytics-service'), + [types.DetectionStrategy]: require('../../services/stix/detection-strategies-service'), + }; + + // Software types share a single service + const softwareService = require('../../services/stix/software-service'); + _serviceMap[types.Malware] = softwareService; + _serviceMap[types.Tool] = softwareService; + + return _serviceMap; +} + +/** + * Resolve the latest `stix.modified` timestamp for a given STIX object ID. + * + * @param {string} objectRef - A STIX identifier (e.g. "attack-pattern--") + * @returns {Promise} The most recent modified timestamp for the object + * @throws {BadRequestError} If the object type is not recognized + * @throws {NotFoundError} If the object does not exist in the database + */ +exports.resolveLatestModified = async function resolveLatestModified(objectRef) { + const type = objectRef.split('--')[0]; + const serviceMap = getServiceMap(); + const service = serviceMap[type]; + + if (!service) { + throw new BadRequestError({ + message: `Unknown object type: ${type}`, + details: `Cannot resolve latest version for object ref "${objectRef}"`, + }); + } + + // retrieveById with { versions: 'latest' } returns a single-element array + // sorted by stix.modified descending. We use 'latest' to be efficient. + const results = await service.retrieveById(objectRef, { versions: 'latest' }); + + if (!results || results.length === 0) { + throw new NotFoundError({ + details: `Object ${objectRef} not found — cannot resolve latest modified timestamp`, + }); + } + + return new Date(results[0].stix.modified); +}; diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index 232d6311..fd894021 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -1,5 +1,5 @@ /* eslint-disable no-unused-vars */ -// TODO remove the above eslint rule after all functions have been implemented +// TODO remove the above eslint rule after all sub-services have been implemented 'use strict'; // ============================================================================= @@ -8,11 +8,17 @@ // Orchestrator that delegates to domain-specific sub-services. This is the // single entry point consumed by the controller layer. // -// All methods currently throw NotImplementedError. Sub-services (WS3-WS7) -// will provide real implementations progressively. +// Phase 1: Track management, snapshot CRUD, config → snapshot-service +// Phase 2: Candidates, staged, object versions → standard-track-service +// Phase 3: Auto-promotion, workflow → workflow-service (TODO) +// Phase 4: Bump/tag, versioning → versioning-service (TODO) +// Phase 5: Virtual track composition → virtual-track-service (TODO) +// Phase 6: Export, ephemeral → export-service, ephemeral-service (TODO) // ============================================================================= const { NotImplementedError } = require('../../exceptions'); +const snapshotService = require('./snapshot-service'); +const standardTrackService = require('./standard-track-service'); const MODULE = 'release-tracks-service'; @@ -21,15 +27,15 @@ function notImplemented(methodName) { } // ----------------------------------------------------------------------------- -// Track management +// Track management (Phase 1 → snapshot-service) // ----------------------------------------------------------------------------- -exports.listTracks = async function listTracks(_options) { - notImplemented('listTracks'); +exports.listTracks = function listTracks(options) { + return snapshotService.listTracks(options); }; -exports.createTrack = async function createTrack(_data) { - notImplemented('createTrack'); +exports.createTrack = function createTrack(data) { + return snapshotService.createTrack(data); }; exports.createTrackFromBundle = async function createTrackFromBundle(_bundleData) { @@ -40,62 +46,58 @@ exports.importTrack = async function importTrack(_data) { notImplemented('importTrack'); }; -exports.getLatestSnapshot = async function getLatestSnapshot(_trackId, _options) { - notImplemented('getLatestSnapshot'); +exports.getLatestSnapshot = function getLatestSnapshot(trackId, options) { + return snapshotService.getLatestSnapshot(trackId, options); }; -exports.getSnapshotByModified = async function getSnapshotByModified( - _trackId, - _modified, - _options, -) { - notImplemented('getSnapshotByModified'); +exports.getSnapshotByModified = function getSnapshotByModified(trackId, modified, options) { + return snapshotService.getSnapshotByModified(trackId, modified, options); }; -exports.updateMetadata = async function updateMetadata(_trackId, _updates, _userId) { - notImplemented('updateMetadata'); +exports.updateMetadata = function updateMetadata(trackId, updates, userId) { + return snapshotService.updateMetadata(trackId, updates, userId); }; -exports.updateMetadataByModified = async function updateMetadataByModified( - _trackId, - _modified, - _updates, - _userId, +exports.updateMetadataByModified = function updateMetadataByModified( + trackId, + modified, + updates, + userId, ) { - notImplemented('updateMetadataByModified'); + return snapshotService.updateMetadataByModified(trackId, modified, updates, userId); }; -exports.updateContents = async function updateContents(_trackId, _contents, _userId) { - notImplemented('updateContents'); +exports.updateContents = function updateContents(trackId, contents, userId) { + return snapshotService.updateContents(trackId, contents, userId); }; -exports.updateContentsByModified = async function updateContentsByModified( - _trackId, - _modified, - _contents, - _userId, +exports.updateContentsByModified = function updateContentsByModified( + trackId, + modified, + contents, + userId, ) { - notImplemented('updateContentsByModified'); + return snapshotService.updateContentsByModified(trackId, modified, contents, userId); }; -exports.cloneTrack = async function cloneTrack(_trackId, _options) { - notImplemented('cloneTrack'); +exports.cloneTrack = function cloneTrack(trackId, options) { + return snapshotService.cloneTrack(trackId, options); }; -exports.cloneFromSnapshot = async function cloneFromSnapshot(_trackId, _modified, _options) { - notImplemented('cloneFromSnapshot'); +exports.cloneFromSnapshot = function cloneFromSnapshot(trackId, modified, options) { + return snapshotService.cloneFromSnapshot(trackId, modified, options); }; -exports.deleteTrack = async function deleteTrack(_trackId) { - notImplemented('deleteTrack'); +exports.deleteTrack = function deleteTrack(trackId) { + return snapshotService.deleteTrack(trackId); }; -exports.deleteSnapshot = async function deleteSnapshot(_trackId, _modified) { - notImplemented('deleteSnapshot'); +exports.deleteSnapshot = function deleteSnapshot(trackId, modified) { + return snapshotService.deleteSnapshot(trackId, modified); }; // ----------------------------------------------------------------------------- -// Ephemeral +// Ephemeral (Phase 6 → ephemeral-service, TODO) // ----------------------------------------------------------------------------- exports.getEphemeralBundle = async function getEphemeralBundle(_domain, _format) { @@ -103,51 +105,47 @@ exports.getEphemeralBundle = async function getEphemeralBundle(_domain, _format) }; // ----------------------------------------------------------------------------- -// Candidates +// Candidates (Phase 2 → standard-track-service) // ----------------------------------------------------------------------------- -exports.addCandidates = async function addCandidates(_trackId, _objectRefs, _userId) { - notImplemented('addCandidates'); +exports.addCandidates = function addCandidates(trackId, objectRefs, userId) { + return standardTrackService.addCandidates(trackId, objectRefs, userId); }; -exports.listCandidates = async function listCandidates(_trackId, _options) { - notImplemented('listCandidates'); +exports.listCandidates = function listCandidates(trackId, options) { + return standardTrackService.listCandidates(trackId, options); }; -exports.removeCandidate = async function removeCandidate(_trackId, _objectRef) { - notImplemented('removeCandidate'); +exports.removeCandidate = function removeCandidate(trackId, objectRef) { + return standardTrackService.removeCandidate(trackId, objectRef); }; -exports.reviewCandidates = async function reviewCandidates(_trackId, _reviewData, _userId) { - notImplemented('reviewCandidates'); +exports.reviewCandidates = function reviewCandidates(trackId, reviewData, userId) { + return standardTrackService.reviewCandidates(trackId, reviewData, userId); }; -exports.promoteCandidates = async function promoteCandidates(_trackId, _objectRefs, _userId) { - notImplemented('promoteCandidates'); +exports.promoteCandidates = function promoteCandidates(trackId, objectRefs, userId) { + return standardTrackService.promoteCandidates(trackId, objectRefs, userId); }; -exports.updateCandidateVersion = async function updateCandidateVersion( - _trackId, - _objectRef, - _data, -) { - notImplemented('updateCandidateVersion'); +exports.updateCandidateVersion = function updateCandidateVersion(trackId, objectRef, data) { + return standardTrackService.updateCandidateVersion(trackId, objectRef, data); }; // ----------------------------------------------------------------------------- -// Staged +// Staged (Phase 2 → standard-track-service) // ----------------------------------------------------------------------------- -exports.listStaged = async function listStaged(_trackId) { - notImplemented('listStaged'); +exports.listStaged = function listStaged(trackId) { + return standardTrackService.listStaged(trackId); }; -exports.demoteStaged = async function demoteStaged(_trackId, _objectRefs, _userId) { - notImplemented('demoteStaged'); +exports.demoteStaged = function demoteStaged(trackId, objectRefs, userId) { + return standardTrackService.demoteStaged(trackId, objectRefs, userId); }; // ----------------------------------------------------------------------------- -// Versioning +// Versioning (Phase 4 → versioning-service, TODO) // ----------------------------------------------------------------------------- exports.bumpLatest = async function bumpLatest(_trackId, _options) { @@ -163,19 +161,19 @@ exports.previewBump = async function previewBump(_trackId, _format) { }; // ----------------------------------------------------------------------------- -// Configuration +// Configuration (Phase 1 → snapshot-service) // ----------------------------------------------------------------------------- -exports.getConfig = async function getConfig(_trackId) { - notImplemented('getConfig'); +exports.getConfig = function getConfig(trackId) { + return snapshotService.getConfig(trackId); }; -exports.updateConfig = async function updateConfig(_trackId, _config, _userId) { - notImplemented('updateConfig'); +exports.updateConfig = function updateConfig(trackId, config, userId) { + return snapshotService.updateConfig(trackId, config, userId); }; // ----------------------------------------------------------------------------- -// Virtual tracks +// Virtual tracks (Phase 5 → virtual-track-service, TODO) // ----------------------------------------------------------------------------- exports.updateComposition = async function updateComposition(_trackId, _composition, _userId) { @@ -191,9 +189,9 @@ exports.previewVirtualSnapshot = async function previewVirtualSnapshot(_trackId) }; // ----------------------------------------------------------------------------- -// Object versions +// Object versions (Phase 2 → standard-track-service) // ----------------------------------------------------------------------------- -exports.listObjectVersions = async function listObjectVersions(_trackId, _objectRef) { - notImplemented('listObjectVersions'); +exports.listObjectVersions = function listObjectVersions(trackId, objectRef) { + return standardTrackService.listObjectVersions(trackId, objectRef); }; diff --git a/app/services/release-tracks/snapshot-service.js b/app/services/release-tracks/snapshot-service.js new file mode 100644 index 00000000..8c93af26 --- /dev/null +++ b/app/services/release-tracks/snapshot-service.js @@ -0,0 +1,477 @@ +'use strict'; + +// ============================================================================= +// Snapshot Service +// +// Core snapshot lifecycle operations: track creation, retrieval, cloning, +// metadata/contents updates, configuration, and deletion. +// +// This is the foundational sub-service consumed by the facade and by other +// sub-services (standard-track, versioning, virtual-track) that need to +// clone or read snapshots. +// ============================================================================= + +const { v4: uuidv4 } = require('uuid'); + +const registryRepo = require('../../repository/release-tracks/release-track-registry.repository'); +const dynamicRepo = require('../../repository/release-tracks/release-track-dynamic.repository'); +const modelFactory = require('../../models/release-tracks/model-factory'); +const logger = require('../../lib/logger'); +const { TrackNotFoundError, NotFoundError } = require('../../exceptions'); + +// ============================================================================= +// Internal helpers +// ============================================================================= + +/** + * Deep-clone a snapshot document, stripping Mongoose metadata. + * + * @param {Object} snapshot - The source snapshot (lean Mongoose document) + * @returns {Object} Plain object copy safe for mutation + */ +function deepClone(snapshot) { + const clone = JSON.parse(JSON.stringify(snapshot)); + delete clone._id; + delete clone.__v; + return clone; +} + +/** + * Recompute and persist denormalized registry counters from actual snapshot data. + * + * @param {string} trackId + */ +async function syncRegistryCounters(trackId) { + const { data: snapshots } = await dynamicRepo.getAllSnapshots(trackId, { + projection: 'modified version', + }); + + const snapshotCount = snapshots.length; + const tagged = snapshots.filter((s) => s.version != null); + const taggedReleaseCount = tagged.length; + + // Latest snapshot is first (sorted desc by modified) + const latestSnapshotModified = snapshots.length > 0 ? snapshots[0].modified : null; + + // Latest tagged version: find the tagged snapshot with the highest modified + const latestTaggedVersion = tagged.length > 0 ? tagged[0].version : null; + + await registryRepo.updateByTrackId(trackId, { + snapshot_count: snapshotCount, + tagged_release_count: taggedReleaseCount, + latest_snapshot_modified: latestSnapshotModified, + latest_tagged_version: latestTaggedVersion, + updated_at: new Date(), + }); +} + +// ============================================================================= +// Track management +// ============================================================================= + +/** + * List all release tracks from the registry. + * + * @param {Object} options - { type?, search?, limit?, offset? } + * @returns {Promise<{ data: Object[], pagination: Object }>} + */ +exports.listTracks = async function listTracks(options) { + return registryRepo.findAll(options); +}; + +/** + * Create a new release track with an initial empty draft snapshot. + * + * @param {Object} data - { name, description?, type, userAccountId?, object_marking_refs?, composition?, snapshot_schedule? } + * @returns {Promise} The initial snapshot document + */ +exports.createTrack = async function createTrack(data) { + const trackId = `release-track--${uuidv4()}`; + const now = new Date(); + const trackType = data.type || 'standard'; + + const initialSnapshot = { + id: trackId, + type: trackType, + modified: now, + version: null, + name: data.name, + description: data.description || '', + created: now, + created_by_ref: data.userAccountId || undefined, + object_marking_refs: data.object_marking_refs, + members: [], + staged: trackType === 'standard' ? [] : undefined, + candidates: trackType === 'standard' ? [] : undefined, + quarantine: trackType === 'virtual' ? [] : undefined, + composition: trackType === 'virtual' ? data.composition : undefined, + config: {}, + version_history: [], + }; + + // Create collection + indexes, then persist the initial snapshot + await modelFactory.ensureIndexes(trackId); + const snapshot = await dynamicRepo.saveSnapshot(trackId, initialSnapshot); + + // Register in the central registry + await registryRepo.create({ + track_id: trackId, + type: trackType, + name: data.name, + description: data.description, + latest_snapshot_modified: now, + snapshot_count: 1, + tagged_release_count: 0, + created_at: now, + updated_at: now, + snapshot_schedule: trackType === 'virtual' ? data.snapshot_schedule : undefined, + }); + + logger.verbose(`SnapshotService: Created ${trackType} track "${data.name}" (${trackId})`); + return snapshot; +}; + +// ============================================================================= +// Snapshot retrieval +// ============================================================================= + +/** + * Retrieve the most recent snapshot for a track. + * + * @param {string} trackId + * @param {Object} [_options] - Reserved for future format/include options + * @returns {Promise} The latest snapshot document + * @throws {TrackNotFoundError} If no snapshots exist for the track + */ +// eslint-disable-next-line no-unused-vars +exports.getLatestSnapshot = async function getLatestSnapshot(trackId, _options) { + const snapshot = await dynamicRepo.getLatestSnapshot(trackId); + if (!snapshot) { + throw new TrackNotFoundError(trackId); + } + return snapshot; +}; + +/** + * Retrieve a specific snapshot by its modified timestamp. + * + * @param {string} trackId + * @param {string|Date} modified + * @param {Object} [_options] - Reserved for future format/include options + * @returns {Promise} The snapshot document + * @throws {NotFoundError} If the snapshot does not exist + */ +// eslint-disable-next-line no-unused-vars +exports.getSnapshotByModified = async function getSnapshotByModified(trackId, modified, _options) { + const snapshot = await dynamicRepo.getSnapshotByModified(trackId, modified); + if (!snapshot) { + throw new NotFoundError({ + details: `Snapshot with modified '${modified}' not found for track '${trackId}'`, + }); + } + return snapshot; +}; + +// ============================================================================= +// Snapshot cloning (internal helper, also used by other sub-services) +// ============================================================================= + +/** + * Clone a snapshot with overrides, persisting the result as a new draft. + * + * Every mutation (metadata update, contents update, tier change) produces a + * new snapshot via this method. Clones are always drafts (version = null). + * + * @param {string} trackId - The track to save the clone into + * @param {Object} sourceSnapshot - The snapshot to clone + * @param {Object} [overrides] - Fields to merge into the clone + * @returns {Promise} The saved clone + */ +exports.cloneSnapshot = async function cloneSnapshot(trackId, sourceSnapshot, overrides) { + const clone = deepClone(sourceSnapshot); + clone.modified = new Date(); + clone.version = null; // clones are always drafts + + // Apply overrides + if (overrides) { + for (const [key, value] of Object.entries(overrides)) { + if (value !== undefined) { + clone[key] = value; + } + } + } + + const saved = await dynamicRepo.saveSnapshot(trackId, clone); + await syncRegistryCounters(trackId); + + logger.verbose(`SnapshotService: Cloned snapshot for track "${trackId}"`); + return saved; +}; + +// ============================================================================= +// Track cloning +// ============================================================================= + +/** + * Clone a track by duplicating its latest snapshot into a new track. + * + * @param {string} trackId - Source track ID + * @param {Object} options - { name?, userAccountId? } + * @returns {Promise} The initial snapshot of the new track + */ +exports.cloneTrack = async function cloneTrack(trackId, options) { + const source = await exports.getLatestSnapshot(trackId); + return _cloneToNewTrack(source, options); +}; + +/** + * Clone a track from a specific snapshot into a new track. + * + * @param {string} trackId - Source track ID + * @param {string|Date} modified - Source snapshot timestamp + * @param {Object} options - { name?, userAccountId? } + * @returns {Promise} The initial snapshot of the new track + */ +exports.cloneFromSnapshot = async function cloneFromSnapshot(trackId, modified, options) { + const source = await exports.getSnapshotByModified(trackId, modified); + return _cloneToNewTrack(source, options); +}; + +/** + * Internal: create a new track from a source snapshot. + */ +async function _cloneToNewTrack(sourceSnapshot, options = {}) { + const newTrackId = `release-track--${uuidv4()}`; + const now = new Date(); + + const clone = deepClone(sourceSnapshot); + clone.id = newTrackId; + clone.modified = now; + clone.version = null; + clone.name = options.name || `${sourceSnapshot.name} (copy)`; + clone.created = now; + clone.created_by_ref = options.userAccountId || sourceSnapshot.created_by_ref; + clone.version_history = []; + + await modelFactory.ensureIndexes(newTrackId); + const saved = await dynamicRepo.saveSnapshot(newTrackId, clone); + + await registryRepo.create({ + track_id: newTrackId, + type: sourceSnapshot.type, + name: clone.name, + description: sourceSnapshot.description, + latest_snapshot_modified: now, + snapshot_count: 1, + tagged_release_count: 0, + created_at: now, + updated_at: now, + }); + + logger.verbose(`SnapshotService: Cloned track to new track "${clone.name}" (${newTrackId})`); + return saved; +} + +// ============================================================================= +// Metadata updates +// ============================================================================= + +/** + * Update metadata on the latest snapshot (creates a new snapshot clone). + * + * @param {string} trackId + * @param {Object} updates - { name?, description?, object_marking_refs? } + * @param {string} [_userId] + * @returns {Promise} The new snapshot + */ +// eslint-disable-next-line no-unused-vars +exports.updateMetadata = async function updateMetadata(trackId, updates, _userId) { + const source = await exports.getLatestSnapshot(trackId); + const overrides = {}; + if (updates.name !== undefined) overrides.name = updates.name; + if (updates.description !== undefined) overrides.description = updates.description; + if (updates.object_marking_refs !== undefined) + overrides.object_marking_refs = updates.object_marking_refs; + + // Also update the registry name/description if changed + const registryUpdates = {}; + if (updates.name !== undefined) registryUpdates.name = updates.name; + if (updates.description !== undefined) registryUpdates.description = updates.description; + if (Object.keys(registryUpdates).length > 0) { + registryUpdates.updated_at = new Date(); + await registryRepo.updateByTrackId(trackId, registryUpdates); + } + + return exports.cloneSnapshot(trackId, source, overrides); +}; + +/** + * Update metadata on a specific snapshot (creates a new snapshot clone). + * + * @param {string} trackId + * @param {string|Date} modified + * @param {Object} updates - { name?, description?, object_marking_refs? } + * @param {string} [_userId] + * @returns {Promise} The new snapshot + */ +exports.updateMetadataByModified = async function updateMetadataByModified( + trackId, + modified, + updates, + // eslint-disable-next-line no-unused-vars + _userId, +) { + const source = await exports.getSnapshotByModified(trackId, modified); + const overrides = {}; + if (updates.name !== undefined) overrides.name = updates.name; + if (updates.description !== undefined) overrides.description = updates.description; + if (updates.object_marking_refs !== undefined) + overrides.object_marking_refs = updates.object_marking_refs; + + const registryUpdates = {}; + if (updates.name !== undefined) registryUpdates.name = updates.name; + if (updates.description !== undefined) registryUpdates.description = updates.description; + if (Object.keys(registryUpdates).length > 0) { + registryUpdates.updated_at = new Date(); + await registryRepo.updateByTrackId(trackId, registryUpdates); + } + + return exports.cloneSnapshot(trackId, source, overrides); +}; + +// ============================================================================= +// Contents updates +// ============================================================================= + +/** + * Replace member contents on the latest snapshot (creates a new snapshot clone). + * + * @param {string} trackId + * @param {Object} contents - { x_mitre_contents: [{ obj_ref, obj_modified }] } + * @param {string} [_userId] + * @returns {Promise} The new snapshot + */ +// eslint-disable-next-line no-unused-vars +exports.updateContents = async function updateContents(trackId, contents, _userId) { + const source = await exports.getLatestSnapshot(trackId); + const members = contents.x_mitre_contents.map((c) => ({ + object_ref: c.obj_ref, + object_modified: c.obj_modified === 'latest' ? new Date() : new Date(c.obj_modified), + })); + return exports.cloneSnapshot(trackId, source, { members }); +}; + +/** + * Replace member contents on a specific snapshot (creates a new snapshot clone). + * + * @param {string} trackId + * @param {string|Date} modified + * @param {Object} contents - { x_mitre_contents: [{ obj_ref, obj_modified }] } + * @param {string} [_userId] + * @returns {Promise} The new snapshot + */ +exports.updateContentsByModified = async function updateContentsByModified( + trackId, + modified, + contents, + // eslint-disable-next-line no-unused-vars + _userId, +) { + const source = await exports.getSnapshotByModified(trackId, modified); + const members = contents.x_mitre_contents.map((c) => ({ + object_ref: c.obj_ref, + object_modified: c.obj_modified === 'latest' ? new Date() : new Date(c.obj_modified), + })); + return exports.cloneSnapshot(trackId, source, { members }); +}; + +// ============================================================================= +// Configuration +// ============================================================================= + +/** + * Get the configuration from the latest snapshot. + * + * @param {string} trackId + * @returns {Promise} The config sub-document + */ +exports.getConfig = async function getConfig(trackId) { + const snapshot = await exports.getLatestSnapshot(trackId); + return snapshot.config || {}; +}; + +/** + * Update configuration on the latest snapshot (creates a new snapshot clone). + * + * Performs a shallow merge at the top level, and a nested merge for + * the `promotion_conflicts` sub-object. + * + * @param {string} trackId + * @param {Object} config - Partial config to merge + * @param {string} [_userId] + * @returns {Promise} The new snapshot + */ +// eslint-disable-next-line no-unused-vars +exports.updateConfig = async function updateConfig(trackId, config, _userId) { + const source = await exports.getLatestSnapshot(trackId); + const existing = source.config || {}; + + const mergedConfig = { ...existing }; + + if (config.candidacy_threshold !== undefined) + mergedConfig.candidacy_threshold = config.candidacy_threshold; + if (config.auto_promote !== undefined) mergedConfig.auto_promote = config.auto_promote; + if (config.include_candidates_in_snapshots !== undefined) + mergedConfig.include_candidates_in_snapshots = config.include_candidates_in_snapshots; + if (config.promotion_conflicts !== undefined) { + mergedConfig.promotion_conflicts = { + ...(existing.promotion_conflicts || {}), + ...config.promotion_conflicts, + }; + } + + return exports.cloneSnapshot(trackId, source, { config: mergedConfig }); +}; + +// ============================================================================= +// Deletion +// ============================================================================= + +/** + * Delete an entire release track (registry entry + all snapshots + collection). + * + * @param {string} trackId + * @throws {TrackNotFoundError} If the track does not exist in the registry + */ +exports.deleteTrack = async function deleteTrack(trackId) { + const registry = await registryRepo.findByTrackId(trackId); + if (!registry) { + throw new TrackNotFoundError(trackId); + } + + await dynamicRepo.dropCollection(trackId); + await registryRepo.deleteByTrackId(trackId); + + logger.verbose(`SnapshotService: Deleted track "${trackId}"`); +}; + +/** + * Delete a specific snapshot from a track. + * + * @param {string} trackId + * @param {string|Date} modified + * @throws {NotFoundError} If the snapshot does not exist + */ +exports.deleteSnapshot = async function deleteSnapshot(trackId, modified) { + const snapshot = await dynamicRepo.getSnapshotByModified(trackId, modified); + if (!snapshot) { + throw new NotFoundError({ + details: `Snapshot with modified '${modified}' not found for track '${trackId}'`, + }); + } + + await dynamicRepo.deleteSnapshot(trackId, modified); + await syncRegistryCounters(trackId); + + logger.verbose(`SnapshotService: Deleted snapshot '${modified}' from track "${trackId}"`); +}; diff --git a/app/services/release-tracks/standard-track-service.js b/app/services/release-tracks/standard-track-service.js new file mode 100644 index 00000000..d2870aed --- /dev/null +++ b/app/services/release-tracks/standard-track-service.js @@ -0,0 +1,496 @@ +'use strict'; + +// ============================================================================= +// Standard Track Service +// +// Manages the three-tier workflow for standard release tracks: +// candidates → staged → members +// +// Operations: add/list/remove/review candidates, promote candidates to staged, +// update candidate version pins, list/demote staged, list object versions. +// +// All mutations produce a new snapshot clone (immutable snapshot model). +// ============================================================================= + +const snapshotService = require('./snapshot-service'); +const objectResolver = require('../../lib/release-tracks/object-resolver'); +const conflictResolution = require('../../lib/release-tracks/conflict-resolution'); +const logger = require('../../lib/logger'); +const { NotFoundError, BadRequestError } = require('../../exceptions'); + +// ============================================================================= +// Internal helpers +// ============================================================================= + +const STATUS_RANK = { + 'work-in-progress': 0, + 'awaiting-review': 1, + reviewed: 2, +}; + +/** + * Validate that the snapshot belongs to a standard track. + * @param {Object} snapshot + * @throws {BadRequestError} + */ +function assertStandardTrack(snapshot) { + if (snapshot.type === 'virtual') { + throw new BadRequestError({ + message: 'This operation is only available for standard release tracks', + details: `Track ${snapshot.id} is a virtual track`, + }); + } +} + +/** + * Normalize a raw object_ref entry from the request body into a consistent + * shape: `{ id: string, modified: string|undefined }`. + * + * The controller's Zod schema allows either a bare string or an object. + */ +function normalizeObjectRef(entry) { + if (typeof entry === 'string') { + return { id: entry, modified: undefined }; + } + return { id: entry.id, modified: entry.modified }; +} + +// ============================================================================= +// Candidates +// ============================================================================= + +/** + * Add one or more objects as candidates on the latest snapshot. + * + * For each entry: + * - If `modified` is "latest" or omitted, resolve via the STIX service layer. + * - Skip duplicates (same object_ref + object_modified already in candidates). + * - New candidates start as "work-in-progress". + * + * @param {string} trackId + * @param {Array} objectRefs + * @param {string} [userId] + * @returns {Promise} The new snapshot + */ +exports.addCandidates = async function addCandidates(trackId, objectRefs, userId) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(source); + + const now = new Date(); + const existingCandidates = source.candidates || []; + const newEntries = []; + + for (const raw of objectRefs) { + const entry = normalizeObjectRef(raw); + + // Resolve modified timestamp + let modified; + if (!entry.modified || entry.modified === 'latest') { + modified = await objectResolver.resolveLatestModified(entry.id); + } else { + modified = new Date(entry.modified); + } + + // Skip if this exact (object_ref + object_modified) already exists in candidates + const isDuplicate = existingCandidates.some( + (c) => + c.object_ref === entry.id && new Date(c.object_modified).getTime() === modified.getTime(), + ); + if (isDuplicate) { + logger.verbose( + `StandardTrackService: Skipping duplicate candidate ${entry.id} @ ${modified.toISOString()}`, + ); + continue; + } + + newEntries.push({ + object_ref: entry.id, + object_modified: modified, + object_status: 'work-in-progress', + object_added_at: now, + object_added_by: userId, + }); + } + + const mergedCandidates = [...existingCandidates, ...newEntries]; + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + candidates: mergedCandidates, + }); + + logger.verbose( + `StandardTrackService: Added ${newEntries.length} candidate(s) to track "${trackId}"`, + ); + return snapshot; +}; + +/** + * List candidates from the latest snapshot, optionally filtered by status. + * + * @param {string} trackId + * @param {Object} [options] - { status? } + * @returns {Promise<{ candidates: Array }>} + */ +exports.listCandidates = async function listCandidates(trackId, options = {}) { + const snapshot = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(snapshot); + + let candidates = snapshot.candidates || []; + + if (options.status) { + candidates = candidates.filter((c) => c.object_status === options.status); + } + + return { candidates }; +}; + +/** + * Remove all candidate entries for a given object ref from the latest snapshot. + * + * @param {string} trackId + * @param {string} objectRef - The STIX ID of the object to remove + * @returns {Promise} The new snapshot + * @throws {NotFoundError} If no candidate with that object_ref exists + */ +exports.removeCandidate = async function removeCandidate(trackId, objectRef) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(source); + + const existingCandidates = source.candidates || []; + const remaining = existingCandidates.filter((c) => c.object_ref !== objectRef); + + if (remaining.length === existingCandidates.length) { + throw new NotFoundError({ + details: `Candidate with object_ref "${objectRef}" not found in track "${trackId}"`, + }); + } + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + candidates: remaining, + }); + + logger.verbose(`StandardTrackService: Removed candidate "${objectRef}" from track "${trackId}"`); + return snapshot; +}; + +/** + * Transition the workflow status of matching candidates. + * + * Status transitions are forward-only: + * work-in-progress → awaiting-review → reviewed + * + * If `reviewData.object_refs` is provided, only those candidates are affected. + * Otherwise all candidates matching `from` status are transitioned. + * + * @param {string} trackId + * @param {Object} reviewData - { from, to, object_refs? } + * @param {string} [userId] + * @returns {Promise} The new snapshot + */ +// eslint-disable-next-line no-unused-vars +exports.reviewCandidates = async function reviewCandidates(trackId, reviewData, userId) { + const { from, to, object_refs: filterRefs } = reviewData; + + // Validate forward-only transition + if (STATUS_RANK[to] <= STATUS_RANK[from]) { + throw new BadRequestError({ + message: `Invalid status transition: cannot move from "${from}" to "${to}"`, + details: + 'Status transitions must be forward-only: work-in-progress → awaiting-review → reviewed', + }); + } + + const source = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(source); + + // Build a set of object_refs to target (if specified) + let targetRefs = null; + if (filterRefs && filterRefs.length > 0) { + targetRefs = new Set(filterRefs.map((r) => (typeof r === 'string' ? r : r.id))); + } + + const updatedCandidates = (source.candidates || []).map((candidate) => { + // Only transition candidates matching the `from` status + if (candidate.object_status !== from) return candidate; + + // If specific refs requested, skip non-matching + if (targetRefs && !targetRefs.has(candidate.object_ref)) return candidate; + + return { + ...candidate, + object_status: to, + }; + }); + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + candidates: updatedCandidates, + }); + + // NOTE: Auto-promotion via workflowService.evaluateAutoPromotion will be + // integrated in Phase 3. For now, status transitions are persisted without + // automatic promotion to staged. + + logger.verbose( + `StandardTrackService: Reviewed candidates "${from}" → "${to}" in track "${trackId}"`, + ); + return snapshot; +}; + +/** + * Manually promote candidates to the staged tier. + * + * Applies the `config.promotion_conflicts.candidates_to_staged` policy to + * handle the case where a different version of the same object already + * exists in staged. + * + * @param {string} trackId + * @param {Array} objectRefs - STIX IDs of candidates to promote + * @param {string} [userId] + * @returns {Promise} The new snapshot + */ +exports.promoteCandidates = async function promoteCandidates(trackId, objectRefs, userId) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(source); + + const now = new Date(); + const refSet = new Set(objectRefs); + const existingCandidates = source.candidates || []; + const existingStaged = source.staged || []; + + // Partition candidates into promoted vs remaining + const toPromote = []; + const remainingCandidates = []; + for (const candidate of existingCandidates) { + if (refSet.has(candidate.object_ref)) { + toPromote.push(candidate); + } else { + remainingCandidates.push(candidate); + } + } + + if (toPromote.length === 0) { + throw new NotFoundError({ + details: 'None of the specified object_refs were found in the candidates tier', + }); + } + + // Build staged entries from promoted candidates + const newStagedEntries = toPromote.map((c) => ({ + object_ref: c.object_ref, + object_modified: c.object_modified, + object_status: c.object_status, + object_staged_at: now, + object_staged_by: userId, + })); + + // Apply conflict resolution policy + const policy = + (source.config && + source.config.promotion_conflicts && + source.config.promotion_conflicts.candidates_to_staged) || + 'prefer_latest'; + + const { merged: mergedStaged, rejected } = conflictResolution.applyConflictPolicy( + existingStaged, + newStagedEntries, + policy, + ); + + // If any were rejected, put them back in candidates + const rejectedRefs = new Set(rejected.map((r) => r.object_ref)); + const finalCandidates = [ + ...remainingCandidates, + ...toPromote.filter((c) => rejectedRefs.has(c.object_ref)), + ]; + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + candidates: finalCandidates, + staged: mergedStaged, + }); + + logger.verbose( + `StandardTrackService: Promoted ${toPromote.length - rejected.length} candidate(s), ` + + `rejected ${rejected.length} in track "${trackId}"`, + ); + return snapshot; +}; + +/** + * Update the version pin (object_modified) of a specific candidate. + * + * @param {string} trackId + * @param {string} objectRef - The STIX ID + * @param {Object} data - { old_modified, new_modified } + * @returns {Promise} The new snapshot + * @throws {NotFoundError} If no matching candidate is found + */ +exports.updateCandidateVersion = async function updateCandidateVersion(trackId, objectRef, data) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(source); + + const oldTime = new Date(data.old_modified).getTime(); + const existingCandidates = source.candidates || []; + + let found = false; + const updatedCandidates = existingCandidates.map((candidate) => { + if ( + candidate.object_ref === objectRef && + new Date(candidate.object_modified).getTime() === oldTime + ) { + found = true; + return { + ...candidate, + object_modified: new Date(data.new_modified), + }; + } + return candidate; + }); + + if (!found) { + throw new NotFoundError({ + details: + `Candidate "${objectRef}" with modified "${data.old_modified}" ` + + `not found in track "${trackId}"`, + }); + } + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + candidates: updatedCandidates, + }); + + logger.verbose( + `StandardTrackService: Updated version pin for "${objectRef}" in track "${trackId}"`, + ); + return snapshot; +}; + +// ============================================================================= +// Staged +// ============================================================================= + +/** + * List all staged entries from the latest snapshot. + * + * @param {string} trackId + * @returns {Promise<{ staged: Array }>} + */ +exports.listStaged = async function listStaged(trackId) { + const snapshot = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(snapshot); + + return { staged: snapshot.staged || [] }; +}; + +/** + * Demote staged entries back to the candidates tier. + * + * Each ref in `objectRefs` is `{ id, modified }` to uniquely identify the + * staged entry. Demoted entries preserve their workflow status. + * + * @param {string} trackId + * @param {Array<{id:string, modified:string}>} objectRefs + * @param {string} [userId] + * @returns {Promise} The new snapshot + */ +exports.demoteStaged = async function demoteStaged(trackId, objectRefs, userId) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(source); + + const now = new Date(); + const existingStaged = source.staged || []; + const existingCandidates = source.candidates || []; + + // Build a lookup key for the refs to demote + const demoteKeys = new Set(objectRefs.map((r) => `${r.id}::${new Date(r.modified).getTime()}`)); + + const remainingStaged = []; + const demotedEntries = []; + + for (const staged of existingStaged) { + const key = `${staged.object_ref}::${new Date(staged.object_modified).getTime()}`; + if (demoteKeys.has(key)) { + // Convert back to a candidate entry, preserving workflow status + demotedEntries.push({ + object_ref: staged.object_ref, + object_modified: staged.object_modified, + object_status: staged.object_status || 'work-in-progress', + object_added_at: now, + object_added_by: userId, + }); + } else { + remainingStaged.push(staged); + } + } + + if (demotedEntries.length === 0) { + throw new NotFoundError({ + details: 'None of the specified object_refs were found in the staged tier', + }); + } + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + staged: remainingStaged, + candidates: [...existingCandidates, ...demotedEntries], + }); + + logger.verbose( + `StandardTrackService: Demoted ${demotedEntries.length} staged entry/entries in track "${trackId}"`, + ); + return snapshot; +}; + +// ============================================================================= +// Object versions +// ============================================================================= + +/** + * List all tier occurrences of a given object across members, staged, and + * candidates in the latest snapshot. + * + * @param {string} trackId + * @param {string} objectRef - The STIX ID to search for + * @returns {Promise<{ versions: Array }>} + */ +exports.listObjectVersions = async function listObjectVersions(trackId, objectRef) { + const snapshot = await snapshotService.getLatestSnapshot(trackId); + assertStandardTrack(snapshot); + + const versions = []; + + // Search members + for (const entry of snapshot.members || []) { + if (entry.object_ref === objectRef) { + versions.push({ + tier: 'members', + object_ref: entry.object_ref, + object_modified: entry.object_modified, + }); + } + } + + // Search staged + for (const entry of snapshot.staged || []) { + if (entry.object_ref === objectRef) { + versions.push({ + tier: 'staged', + object_ref: entry.object_ref, + object_modified: entry.object_modified, + object_status: entry.object_status, + }); + } + } + + // Search candidates + for (const entry of snapshot.candidates || []) { + if (entry.object_ref === objectRef) { + versions.push({ + tier: 'candidates', + object_ref: entry.object_ref, + object_modified: entry.object_modified, + object_status: entry.object_status, + }); + } + } + + return { versions }; +}; From 8c1cecb3608c7c34c7b45c513e08db028073cebb Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 2 Feb 2026 17:33:50 -0500 Subject: [PATCH 186/370] chore: fix imports --- app/services/system/validate-service.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index 00c79da5..ead8ec4d 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -19,19 +19,11 @@ const { collectionSchema, markingDefinitionSchema, relationshipPartialSchema, -} = require('@mitre-attack/attack-data-model/dist'); -const { campaignPartialSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/campaign.schema'); -const { groupPartialSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/group.schema'); -const { malwarePartialSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/malware.schema'); -const { - toolPartialSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/tool.schema'); + toolPartialSchema +} = require('@mitre-attack/attack-data-model/dist'); const STIX_SCHEMAS = { 'x-mitre-tactic': tacticSchema, From d8c0d97486092dea23a7b6029b709b1d91535acb Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 2 Feb 2026 17:34:14 -0500 Subject: [PATCH 187/370] chore: fix imports --- app/services/system/validate-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index ead8ec4d..abe91d5b 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -22,7 +22,7 @@ const { campaignPartialSchema, groupPartialSchema, malwarePartialSchema, - toolPartialSchema + toolPartialSchema, } = require('@mitre-attack/attack-data-model/dist'); const STIX_SCHEMAS = { From afe5706e89feb91e3da2e2daa8047761ffdecde1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:21:58 -0500 Subject: [PATCH 188/370] feat(release-tracks): implement auto-promotion functionality - new file (workflow-service.js): provides the core auto-promotion functionality - mod file (standard-track-service): auto-promotion evaluated after adding candidates and after reviewing candidates - mod file (release-tracks-service): updated facade comments to reflect phase 3 completion --- .../release-tracks/release-tracks-service.js | 2 +- .../release-tracks/standard-track-service.js | 32 +++- .../release-tracks/workflow-service.js | 178 ++++++++++++++++++ 3 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 app/services/release-tracks/workflow-service.js diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index fd894021..c4ba4f77 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -10,7 +10,7 @@ // // Phase 1: Track management, snapshot CRUD, config → snapshot-service // Phase 2: Candidates, staged, object versions → standard-track-service -// Phase 3: Auto-promotion, workflow → workflow-service (TODO) +// Phase 3: Auto-promotion, workflow → workflow-service // Phase 4: Bump/tag, versioning → versioning-service (TODO) // Phase 5: Virtual track composition → virtual-track-service (TODO) // Phase 6: Export, ephemeral → export-service, ephemeral-service (TODO) diff --git a/app/services/release-tracks/standard-track-service.js b/app/services/release-tracks/standard-track-service.js index d2870aed..b5bc4787 100644 --- a/app/services/release-tracks/standard-track-service.js +++ b/app/services/release-tracks/standard-track-service.js @@ -18,6 +18,16 @@ const conflictResolution = require('../../lib/release-tracks/conflict-resolution const logger = require('../../lib/logger'); const { NotFoundError, BadRequestError } = require('../../exceptions'); +// Lazy-load workflowService to avoid circular dependency +// (workflow-service imports snapshot-service, which is also imported here) +let workflowService; +function getWorkflowService() { + if (!workflowService) { + workflowService = require('./workflow-service'); + } + return workflowService; +} + // ============================================================================= // Internal helpers // ============================================================================= @@ -114,13 +124,20 @@ exports.addCandidates = async function addCandidates(trackId, objectRefs, userId const mergedCandidates = [...existingCandidates, ...newEntries]; - const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + let snapshot = await snapshotService.cloneSnapshot(trackId, source, { candidates: mergedCandidates, }); logger.verbose( `StandardTrackService: Added ${newEntries.length} candidate(s) to track "${trackId}"`, ); + + // Evaluate auto-promotion for newly added candidates (Phase 3) + const autoPromotedSnapshot = await getWorkflowService().evaluateAutoPromotion(trackId, snapshot); + if (autoPromotedSnapshot) { + snapshot = autoPromotedSnapshot; + } + return snapshot; }; @@ -222,17 +239,20 @@ exports.reviewCandidates = async function reviewCandidates(trackId, reviewData, }; }); - const snapshot = await snapshotService.cloneSnapshot(trackId, source, { + let snapshot = await snapshotService.cloneSnapshot(trackId, source, { candidates: updatedCandidates, }); - // NOTE: Auto-promotion via workflowService.evaluateAutoPromotion will be - // integrated in Phase 3. For now, status transitions are persisted without - // automatic promotion to staged. - logger.verbose( `StandardTrackService: Reviewed candidates "${from}" → "${to}" in track "${trackId}"`, ); + + // Evaluate auto-promotion after status transition (Phase 3) + const autoPromotedSnapshot = await getWorkflowService().evaluateAutoPromotion(trackId, snapshot); + if (autoPromotedSnapshot) { + snapshot = autoPromotedSnapshot; + } + return snapshot; }; diff --git a/app/services/release-tracks/workflow-service.js b/app/services/release-tracks/workflow-service.js new file mode 100644 index 00000000..8a28884b --- /dev/null +++ b/app/services/release-tracks/workflow-service.js @@ -0,0 +1,178 @@ +'use strict'; + +// ============================================================================= +// Workflow Service +// +// Manages automatic promotion of candidates to staged based on workflow +// status and candidacy threshold configuration. +// +// Core functionality: +// - Evaluates whether a candidate's workflow status meets the track's +// candidacy threshold +// - Automatically promotes qualifying candidates to the staged tier +// - Handles conflict resolution during auto-promotion +// +// This service is invoked by standard-track-service after: +// - Candidate status transitions (reviewCandidates) +// - New candidates are added (addCandidates) +// ============================================================================= + +const snapshotService = require('./snapshot-service'); +const conflictResolution = require('../../lib/release-tracks/conflict-resolution'); +const logger = require('../../lib/logger'); + +// ============================================================================= +// Status ranking and threshold evaluation +// ============================================================================= + +const STATUS_RANK = { + 'work-in-progress': 0, + 'awaiting-review': 1, + reviewed: 2, +}; + +/** + * Check if a candidate's status meets or exceeds the configured threshold. + * + * @param {string} candidateStatus - The candidate's current status + * @param {string} threshold - The configured candidacy threshold + * @returns {boolean} True if the candidate meets the threshold + */ +exports.meetsThreshold = function meetsThreshold(candidateStatus, threshold) { + const candidateRank = STATUS_RANK[candidateStatus]; + const thresholdRank = STATUS_RANK[threshold]; + + if (candidateRank === undefined || thresholdRank === undefined) { + return false; + } + + return candidateRank >= thresholdRank; +}; + +// ============================================================================= +// Auto-promotion evaluation +// ============================================================================= + +/** + * Evaluate and execute auto-promotion for qualifying candidates. + * + * This is called after candidate status changes (via reviewCandidates) or + * when new candidates are added (via addCandidates). If auto_promote is + * enabled and candidates meet the candidacy threshold, they are automatically + * promoted to the staged tier. + * + * @param {string} trackId - The release track ID + * @param {Object} snapshot - The current snapshot (with updated candidates) + * @returns {Promise} The new snapshot if promotion occurred, null otherwise + */ +exports.evaluateAutoPromotion = async function evaluateAutoPromotion(trackId, snapshot) { + // Auto-promotion only applies to standard tracks + if (snapshot.type !== 'standard') { + return null; + } + + // Check if auto-promotion is enabled + const config = snapshot.config || {}; + if (config.auto_promote !== true) { + return null; + } + + // Determine the candidacy threshold (default: 'reviewed') + const threshold = config.candidacy_threshold || 'reviewed'; + + // Find candidates that meet the threshold + const candidates = snapshot.candidates || []; + const qualifying = candidates.filter((c) => exports.meetsThreshold(c.object_status, threshold)); + + if (qualifying.length === 0) { + return null; + } + + logger.verbose( + `WorkflowService: ${qualifying.length} candidate(s) meet threshold "${threshold}" in track "${trackId}"`, + ); + + // Promote qualifying candidates to staged + return _promoteToStaged(trackId, snapshot, qualifying); +}; + +// ============================================================================= +// Internal promotion logic +// ============================================================================= + +/** + * Internal: Promote qualifying candidates to the staged tier. + * + * This performs the actual tier mutation by: + * 1. Building staged entries from qualifying candidates + * 2. Applying conflict resolution policy + * 3. Removing promoted candidates from the candidates tier + * 4. Cloning the snapshot with updated tiers + * + * @param {string} trackId + * @param {Object} snapshot - The source snapshot + * @param {Array} qualifyingCandidates - Candidates to promote + * @returns {Promise} The new snapshot + */ +async function _promoteToStaged(trackId, snapshot, qualifyingCandidates) { + const now = new Date(); + const existingCandidates = snapshot.candidates || []; + const existingStaged = snapshot.staged || []; + + // Build a set of qualifying object_refs for efficient lookup + const qualifyingRefs = new Set(qualifyingCandidates.map((c) => c.object_ref)); + + // Partition candidates into promoted vs remaining + const toPromote = []; + const remainingCandidates = []; + + for (const candidate of existingCandidates) { + if (qualifyingRefs.has(candidate.object_ref)) { + toPromote.push(candidate); + } else { + remainingCandidates.push(candidate); + } + } + + // Build staged entries from promoted candidates + const newStagedEntries = toPromote.map((c) => ({ + object_ref: c.object_ref, + object_modified: c.object_modified, + object_status: c.object_status, + object_staged_at: now, + object_staged_by: c.object_added_by, // Preserve original author + })); + + // Apply conflict resolution policy + const policy = + (snapshot.config && + snapshot.config.promotion_conflicts && + snapshot.config.promotion_conflicts.candidates_to_staged) || + 'prefer_latest'; + + const { merged: mergedStaged, rejected } = conflictResolution.applyConflictPolicy( + existingStaged, + newStagedEntries, + policy, + ); + + // If any were rejected by conflict policy, put them back in candidates + const rejectedRefs = new Set(rejected.map((r) => r.object_ref)); + const finalCandidates = [ + ...remainingCandidates, + ...toPromote.filter((c) => rejectedRefs.has(c.object_ref)), + ]; + + // Clone snapshot with updated tiers + const newSnapshot = await snapshotService.cloneSnapshot(trackId, snapshot, { + candidates: finalCandidates, + staged: mergedStaged, + }); + + logger.verbose( + `WorkflowService: Auto-promoted ${toPromote.length - rejected.length} candidate(s), ` + + `rejected ${rejected.length} in track "${trackId}"`, + ); + + return newSnapshot; +} From 542c7ddc07463e7771dff9c743b17ef66cd28dc3 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:01:09 -0500 Subject: [PATCH 189/370] feat(release-tracks): implement openapi spec files --- .../definitions/components/release-tracks.yml | 343 +++++++ app/api/definitions/openapi.yml | 87 ++ .../paths/release-tracks-paths.yml | 870 ++++++++++++++++++ 3 files changed, 1300 insertions(+) create mode 100644 app/api/definitions/components/release-tracks.yml create mode 100644 app/api/definitions/paths/release-tracks-paths.yml diff --git a/app/api/definitions/components/release-tracks.yml b/app/api/definitions/components/release-tracks.yml new file mode 100644 index 00000000..31ae2de4 --- /dev/null +++ b/app/api/definitions/components/release-tracks.yml @@ -0,0 +1,343 @@ +components: + schemas: + release-track-snapshot: + type: object + description: 'A snapshot document for a release track, containing versioned member objects and workflow tiers' + properties: + id: + type: string + description: 'The release track ID (STIX identifier format)' + example: 'release-track--a1b2c3d4-e5f6-7890-abcd-ef1234567890' + type: + type: string + enum: + - standard + - virtual + description: 'Track type: standard (direct object management) or virtual (aggregation)' + modified: + type: string + format: date-time + description: 'When this snapshot was created' + version: + type: string + nullable: true + description: 'Semantic version (e.g., "1.0", "2.1") if tagged, null for draft snapshots' + example: '1.0' + name: + type: string + description: 'Human-readable track name' + example: 'Enterprise ATT&CK' + description: + type: string + description: 'Track description' + created: + type: string + format: date-time + description: 'When the track was initially created' + created_by_ref: + type: string + description: 'STIX ID of the user who created the track' + example: 'identity--12345678-1234-1234-1234-123456789012' + object_marking_refs: + type: array + items: + type: string + description: 'STIX marking definition references' + members: + type: array + description: 'Released objects (promoted from staged during tagging)' + items: + $ref: '#/components/schemas/tier-entry' + staged: + type: array + nullable: true + description: 'Reviewed objects ready for next release (standard tracks only)' + items: + $ref: '#/components/schemas/staged-entry' + candidates: + type: array + nullable: true + description: 'Objects in workflow (standard tracks only)' + items: + $ref: '#/components/schemas/candidate-entry' + quarantine: + type: array + nullable: true + description: 'Conflicting objects needing resolution (virtual tracks only)' + items: + $ref: '#/components/schemas/tier-entry' + composition: + nullable: true + description: 'Component track references (virtual tracks only)' + $ref: '#/components/schemas/composition' + config: + $ref: '#/components/schemas/track-config' + version_history: + type: array + description: 'History of tagged releases' + items: + $ref: '#/components/schemas/version-history-entry' + + tier-entry: + type: object + description: 'A reference to a specific version of a STIX object' + properties: + object_ref: + type: string + description: 'STIX ID of the object' + example: 'attack-pattern--12345678-1234-1234-1234-123456789012' + object_modified: + type: string + format: date-time + description: 'Version pin: the modified timestamp of this object version' + + candidate-entry: + allOf: + - $ref: '#/components/schemas/tier-entry' + - type: object + properties: + object_status: + type: string + enum: + - work-in-progress + - awaiting-review + - reviewed + description: 'Workflow status (scoped to this track)' + object_added_at: + type: string + format: date-time + description: 'When added to candidates' + object_added_by: + type: string + description: 'User who added this candidate' + + staged-entry: + allOf: + - $ref: '#/components/schemas/tier-entry' + - type: object + properties: + object_status: + type: string + enum: + - work-in-progress + - awaiting-review + - reviewed + description: 'Workflow status (preserved from candidates)' + object_staged_at: + type: string + format: date-time + description: 'When promoted to staged' + object_staged_by: + type: string + description: 'User who staged this object' + + track-config: + type: object + description: 'Release track configuration' + properties: + auto_promote: + type: boolean + description: 'Whether to automatically promote candidates that meet the threshold' + default: false + candidacy_threshold: + type: string + enum: + - work-in-progress + - awaiting-review + - reviewed + description: 'Minimum workflow status required for auto-promotion to staged' + default: 'reviewed' + include_candidates_in_snapshots: + type: boolean + description: 'Whether to include candidates when exporting snapshots' + default: false + promotion_conflicts: + type: object + description: 'Conflict resolution policies for tier promotions' + properties: + candidates_to_staged: + type: string + enum: + - always_overwrite + - always_reject + - prefer_latest + - abort + description: 'How to handle conflicts when promoting candidates to staged' + default: 'prefer_latest' + staged_to_members: + type: string + enum: + - always_overwrite + - always_reject + - prefer_latest + - abort + description: 'How to handle conflicts when tagging (promoting staged to members)' + default: 'abort' + + composition: + type: object + description: 'Virtual track composition (references to component standard tracks)' + properties: + component_tracks: + type: array + items: + $ref: '#/components/schemas/component-track' + deduplication_strategy: + type: string + enum: + - prioritize_latest_object + - prioritize_latest_snapshot + - prioritize_higher_priority + - quarantine + description: 'How to resolve duplicate objects across components' + default: 'prioritize_latest_object' + + component-track: + type: object + description: 'Reference to a component track in a virtual track composition' + properties: + track_id: + type: string + description: 'The release track ID of the component' + example: 'release-track--a1b2c3d4-e5f6-7890-abcd-ef1234567890' + priority: + type: number + description: 'Priority for deduplication (higher wins)' + resolution_strategy: + type: string + enum: + - latest_tagged + - specific_version + - specific_snapshot + description: 'How to resolve which snapshot to use from this component' + default: 'latest_tagged' + version: + type: string + nullable: true + description: 'Specific version to pin to (when resolution_strategy is specific_version)' + snapshot_modified: + type: string + format: date-time + nullable: true + description: 'Specific snapshot to pin to (when resolution_strategy is specific_snapshot)' + filters: + type: object + description: 'Optional filters to apply to component members' + properties: + object_types: + type: array + items: + type: string + description: 'Only include these STIX types' + domains: + type: array + items: + type: string + description: 'Only include objects from these ATT&CK domains' + + version-history-entry: + type: object + description: 'Record of a tagged release' + properties: + version: + type: string + description: 'The semantic version number' + example: '1.0' + tagged_at: + type: string + format: date-time + description: 'When this version was tagged' + tagged_by: + type: string + description: 'User who tagged this version' + snapshot_id: + type: string + format: date-time + description: 'The snapshot modified timestamp that was tagged' + summary: + type: object + description: 'Statistics about this release' + properties: + members_count: + type: number + description: 'Total objects in members tier after release' + promoted_count: + type: number + description: 'Objects promoted from staged in this release' + staged_count: + type: number + description: 'Objects remaining in staged after release' + candidate_count: + type: number + description: 'Objects in candidates at time of release' + + release-track-registry: + type: object + description: 'Registry entry tracking metadata about a release track' + properties: + track_id: + type: string + description: 'The release track ID' + example: 'release-track--a1b2c3d4-e5f6-7890-abcd-ef1234567890' + type: + type: string + enum: + - standard + - virtual + description: 'Track type' + name: + type: string + description: 'Track name' + description: + type: string + description: 'Track description' + latest_snapshot_modified: + type: string + format: date-time + description: 'Timestamp of the most recent snapshot' + latest_tagged_version: + type: string + nullable: true + description: 'Most recent tagged version number' + example: '2.1' + snapshot_count: + type: number + description: 'Total number of snapshots for this track' + tagged_release_count: + type: number + description: 'Number of tagged releases' + created_at: + type: string + format: date-time + description: 'When the track was created' + updated_at: + type: string + format: date-time + description: 'When the track metadata was last updated' + snapshot_schedule: + nullable: true + description: 'Automated snapshot schedule (virtual tracks only)' + $ref: '#/components/schemas/snapshot-schedule' + + snapshot-schedule: + type: object + description: 'Schedule for automated virtual track snapshot creation' + properties: + mode: + type: string + enum: + - interval + - dates + - disabled + description: 'Scheduling mode' + interval_days: + type: number + nullable: true + description: 'Days between snapshots (when mode is interval)' + dates: + type: array + nullable: true + items: + type: string + format: date-time + description: 'Specific dates for snapshots (when mode is dates)' diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 33b3ac3f..391e7081 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -68,6 +68,8 @@ tags: description: 'Operations on system status' - name: 'Reports' description: 'Operations for generating analytical reports on ATT&CK data' + - name: 'Release Tracks' + description: 'Operations on release tracks (versioned STIX object releases with workflow management)' paths: # ATT&CK Objects @@ -282,6 +284,91 @@ paths: /api/stix-bundles: $ref: 'paths/stix-bundles-paths.yml#/paths/~1api~1stix-bundles' + # Release Tracks + /api/release-tracks/ephemeral/{domain}: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1ephemeral~1{domain}' + + /api/release-tracks: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks' + + /api/release-tracks/new: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1new' + + /api/release-tracks/new-from-bundle: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1new-from-bundle' + + /api/release-tracks/import: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1import' + + /api/release-tracks/{id}: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}' + + /api/release-tracks/{id}/meta: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1meta' + + /api/release-tracks/{id}/contents: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1contents' + + /api/release-tracks/{id}/clone: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1clone' + + /api/release-tracks/{id}/bump: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1bump' + + /api/release-tracks/{id}/bump/preview: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1bump~1preview' + + /api/release-tracks/{id}/candidates: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1candidates' + + /api/release-tracks/{id}/candidates/review: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1candidates~1review' + + /api/release-tracks/{id}/candidates/promote: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1candidates~1promote' + + /api/release-tracks/{id}/candidates/{objectRef}: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1candidates~1{objectRef}' + + /api/release-tracks/{id}/candidates/{objectRef}/update-version: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1candidates~1{objectRef}~1update-version' + + /api/release-tracks/{id}/staged: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1staged' + + /api/release-tracks/{id}/staged/demote: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1staged~1demote' + + /api/release-tracks/{id}/config: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1config' + + /api/release-tracks/{id}/objects/{objectRef}/versions: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1objects~1{objectRef}~1versions' + + /api/release-tracks/{id}/composition: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1composition' + + /api/release-tracks/{id}/snapshots/create: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1create' + + /api/release-tracks/{id}/snapshots/preview: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1preview' + + /api/release-tracks/{id}/snapshots/{modified}: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1{modified}' + + /api/release-tracks/{id}/snapshots/{modified}/meta: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1{modified}~1meta' + + /api/release-tracks/{id}/snapshots/{modified}/contents: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1{modified}~1contents' + + /api/release-tracks/{id}/snapshots/{modified}/clone: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1{modified}~1clone' + + /api/release-tracks/{id}/snapshots/{modified}/bump: + $ref: 'paths/release-tracks-paths.yml#/paths/~1api~1release-tracks~1{id}~1snapshots~1{modified}~1bump' + # System Configuration /api/config/system-version: $ref: 'paths/system-configuration-paths.yml#/paths/~1api~1config~1system-version' diff --git a/app/api/definitions/paths/release-tracks-paths.yml b/app/api/definitions/paths/release-tracks-paths.yml new file mode 100644 index 00000000..45b448ee --- /dev/null +++ b/app/api/definitions/paths/release-tracks-paths.yml @@ -0,0 +1,870 @@ +paths: + # ============================================================================= + # Ephemeral bundles + # ============================================================================= + /api/release-tracks/ephemeral/{domain}: + get: + summary: 'Generate an ephemeral bundle for a domain' + operationId: 'release-tracks-ephemeral-get' + description: | + Generate a stateless bundle containing all objects from a given ATT&CK domain. + This endpoint queries all STIX repositories by domain without persisting a release track. + tags: + - 'Release Tracks' + parameters: + - name: domain + in: path + required: true + description: 'ATT&CK domain (e.g., enterprise-attack, mobile-attack, ics-attack)' + schema: + type: string + example: 'enterprise-attack' + - name: format + in: query + description: 'Output format (bundle, workbench, or filesystem-store)' + schema: + type: string + enum: + - bundle + - workbench + - filesystem-store + default: bundle + responses: + '200': + description: 'Ephemeral bundle generated successfully' + '501': + description: 'Not yet implemented' + + # ============================================================================= + # Track management + # ============================================================================= + /api/release-tracks: + get: + summary: 'List all release tracks' + operationId: 'release-tracks-list' + description: | + Retrieve a paginated list of release track registry entries. + Returns metadata about each track (not full snapshots). + tags: + - 'Release Tracks' + parameters: + - name: type + in: query + description: 'Filter by track type' + schema: + type: string + enum: + - standard + - virtual + - name: search + in: query + description: 'Search by name or description (case-insensitive)' + schema: + type: string + - name: limit + in: query + description: 'Number of tracks to return (0 for all)' + schema: + type: number + default: 0 + - name: offset + in: query + description: 'Number of tracks to skip' + schema: + type: number + default: 0 + responses: + '200': + description: 'List of release tracks' + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../components/release-tracks.yml#/components/schemas/release-track-registry' + pagination: + type: object + properties: + total: + type: number + limit: + type: number + offset: + type: number + + /api/release-tracks/new: + post: + summary: 'Create a new release track' + operationId: 'release-tracks-create' + description: | + Create a new standard or virtual release track with an initial empty draft snapshot. + Request body is validated via Zod (not OpenAPI). See controller for schema. + tags: + - 'Release Tracks' + # Request body validation moved to Zod in controller + responses: + '201': + description: 'Release track created successfully' + content: + application/json: + schema: + $ref: '../components/release-tracks.yml#/components/schemas/release-track-snapshot' + '400': + description: 'Invalid request parameters' + + /api/release-tracks/new-from-bundle: + post: + summary: 'Create a release track from a STIX bundle' + operationId: 'release-tracks-create-from-bundle' + description: | + Parse a STIX bundle and create a new release track with member objects extracted from x_mitre_contents. + Request body validation moved to Zod in controller. + tags: + - 'Release Tracks' + responses: + '201': + description: 'Release track created from bundle' + '501': + description: 'Not yet implemented' + + /api/release-tracks/import: + post: + summary: 'Import a release track' + operationId: 'release-tracks-import' + description: | + Import a release track with all snapshots and version history. + Request body validation moved to Zod in controller. + tags: + - 'Release Tracks' + responses: + '201': + description: 'Release track imported successfully' + '501': + description: 'Not yet implemented' + + # ============================================================================= + # Track retrieval and deletion + # ============================================================================= + /api/release-tracks/{id}: + get: + summary: 'Get the latest snapshot of a release track' + operationId: 'release-tracks-get-latest' + description: | + Retrieve the most recent snapshot for a release track. + By default returns only members; use include query param for other tiers. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + description: 'Release track ID' + schema: + type: string + example: 'release-track--a1b2c3d4-e5f6-7890-abcd-ef1234567890' + - name: include + in: query + description: 'Which tiers to include in response' + schema: + type: string + enum: + - members + - staged + - candidates + - all + default: members + - name: format + in: query + description: 'Output format' + schema: + type: string + enum: + - snapshot + - bundle + - workbench + default: snapshot + responses: + '200': + description: 'Latest snapshot retrieved successfully' + content: + application/json: + schema: + $ref: '../components/release-tracks.yml#/components/schemas/release-track-snapshot' + '404': + description: 'Release track not found' + + delete: + summary: 'Delete a release track' + operationId: 'release-tracks-delete' + description: | + Delete an entire release track including all snapshots and version history. + This operation cannot be undone. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + description: 'Release track ID' + schema: + type: string + responses: + '204': + description: 'Release track deleted successfully' + '404': + description: 'Release track not found' + + # ============================================================================= + # Latest snapshot operations + # ============================================================================= + /api/release-tracks/{id}/meta: + post: + summary: 'Update metadata on the latest snapshot' + operationId: 'release-tracks-update-meta-latest' + description: | + Update name, description, or object_marking_refs on the latest snapshot. + Creates a new snapshot clone with updated metadata. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Metadata updated successfully' + content: + application/json: + schema: + $ref: '../components/release-tracks.yml#/components/schemas/release-track-snapshot' + + /api/release-tracks/{id}/contents: + post: + summary: 'Update member contents on the latest snapshot' + operationId: 'release-tracks-update-contents-latest' + description: | + Replace the members tier with new contents (x_mitre_contents format). + Creates a new snapshot clone. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Contents updated successfully' + + /api/release-tracks/{id}/clone: + post: + summary: 'Clone the latest snapshot into a new release track' + operationId: 'release-tracks-clone-latest' + description: | + Create a new release track by cloning the latest snapshot. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '201': + description: 'Release track cloned successfully' + + /api/release-tracks/{id}/bump: + post: + summary: 'Tag the latest snapshot (create a release)' + operationId: 'release-tracks-bump-latest' + description: | + Tag the latest snapshot with a version number. + For standard tracks: promotes staged → members. + For virtual tracks: N/A (already resolved). + Request body validated via Zod in controller: { type: 'major'|'minor', version?: string, dry_run?: boolean } + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Snapshot tagged successfully' + '409': + description: 'Snapshot already tagged or conflict during promotion' + '501': + description: 'Not yet implemented' + + /api/release-tracks/{id}/bump/preview: + get: + summary: 'Preview the next release' + operationId: 'release-tracks-bump-preview' + description: | + Compute what the next tagged release will contain without persisting changes. + Shows which objects will be promoted from staged → members. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: format + in: query + schema: + type: string + enum: + - summary + - detailed + default: summary + responses: + '200': + description: 'Release preview generated' + '501': + description: 'Not yet implemented' + + # ============================================================================= + # Candidate management + # ============================================================================= + /api/release-tracks/{id}/candidates: + get: + summary: 'List candidates in the latest snapshot' + operationId: 'release-tracks-candidates-list' + description: | + Retrieve the candidates tier from the latest snapshot. + Optionally filter by workflow status. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: status + in: query + description: 'Filter by workflow status' + schema: + type: string + enum: + - work-in-progress + - awaiting-review + - reviewed + responses: + '200': + description: 'Candidates retrieved successfully' + content: + application/json: + schema: + type: object + properties: + candidates: + type: array + items: + $ref: '../components/release-tracks.yml#/components/schemas/candidate-entry' + + post: + summary: 'Add objects as candidates' + operationId: 'release-tracks-candidates-add' + description: | + Add one or more objects to the candidates tier. + If modified is omitted or 'latest', resolves to the latest version of the object. + If auto_promote is enabled and candidates meet the threshold, they are auto-promoted to staged. + Request body validated via Zod: { object_refs: Array } + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Candidates added successfully' + + /api/release-tracks/{id}/candidates/review: + post: + summary: 'Bulk transition candidate workflow status' + operationId: 'release-tracks-candidates-review' + description: | + Transition candidates from one workflow status to another (forward-only). + If auto_promote is enabled and candidates meet the threshold after transition, they are auto-promoted to staged. + Request body validated via Zod: { from, to, object_refs? } + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Candidates reviewed successfully' + + /api/release-tracks/{id}/candidates/promote: + post: + summary: 'Manually promote candidates to staged' + operationId: 'release-tracks-candidates-promote' + description: | + Manually promote specific candidates to the staged tier, bypassing auto-promotion logic. + Applies conflict resolution policy. + Request body validated via Zod: { object_refs: string[] } + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Candidates promoted successfully' + + /api/release-tracks/{id}/candidates/{objectRef}: + delete: + summary: 'Remove a candidate' + operationId: 'release-tracks-candidates-remove' + description: | + Remove all entries for a given object_ref from the candidates tier. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: objectRef + in: path + required: true + description: 'STIX ID of the object to remove' + schema: + type: string + responses: + '200': + description: 'Candidate removed successfully' + '404': + description: 'Candidate not found' + + /api/release-tracks/{id}/candidates/{objectRef}/update-version: + post: + summary: 'Update the version pin of a candidate' + operationId: 'release-tracks-candidates-update-version' + description: | + Change which version of an object is being tracked in the candidates tier. + Request body validated via Zod: { old_modified, new_modified } + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: objectRef + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Candidate version pin updated successfully' + + # ============================================================================= + # Staged objects + # ============================================================================= + /api/release-tracks/{id}/staged: + get: + summary: 'List staged objects in the latest snapshot' + operationId: 'release-tracks-staged-list' + description: | + Retrieve the staged tier from the latest snapshot. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Staged objects retrieved successfully' + content: + application/json: + schema: + type: object + properties: + staged: + type: array + items: + $ref: '../components/release-tracks.yml#/components/schemas/staged-entry' + + /api/release-tracks/{id}/staged/demote: + post: + summary: 'Demote staged objects back to candidates' + operationId: 'release-tracks-staged-demote' + description: | + Move objects from staged tier back to candidates tier. + Request body validated via Zod: { object_refs: Array<{id, modified}> } + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Staged objects demoted successfully' + + # ============================================================================= + # Configuration + # ============================================================================= + /api/release-tracks/{id}/config: + get: + summary: 'Get release track configuration' + operationId: 'release-tracks-config-get' + description: | + Retrieve the config sub-document from the latest snapshot. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Configuration retrieved successfully' + content: + application/json: + schema: + $ref: '../components/release-tracks.yml#/components/schemas/track-config' + + put: + summary: 'Update release track configuration' + operationId: 'release-tracks-config-update' + description: | + Merge configuration changes into the latest snapshot (creates new snapshot clone). + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Configuration updated successfully' + + # ============================================================================= + # Object version history + # ============================================================================= + /api/release-tracks/{id}/objects/{objectRef}/versions: + get: + summary: 'List all versions of an object across tiers' + operationId: 'release-tracks-object-versions' + description: | + Find all occurrences of a given object_ref across members, staged, and candidates tiers. + Shows which versions are tracked in which tiers. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: objectRef + in: path + required: true + description: 'STIX ID of the object' + schema: + type: string + responses: + '200': + description: 'Object versions retrieved successfully' + content: + application/json: + schema: + type: object + properties: + versions: + type: array + items: + type: object + properties: + tier: + type: string + enum: + - members + - staged + - candidates + object_ref: + type: string + object_modified: + type: string + format: date-time + object_status: + type: string + nullable: true + + # ============================================================================= + # Virtual track operations + # ============================================================================= + /api/release-tracks/{id}/composition: + put: + summary: 'Update virtual track composition' + operationId: 'release-tracks-composition-update' + description: | + Update which component tracks a virtual track aggregates. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Composition updated successfully' + '501': + description: 'Not yet implemented' + + /api/release-tracks/{id}/snapshots/create: + post: + summary: 'Create a virtual track snapshot' + operationId: 'release-tracks-virtual-snapshot-create' + description: | + Resolve component tracks and create a new virtual snapshot. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '201': + description: 'Virtual snapshot created successfully' + '501': + description: 'Not yet implemented' + + /api/release-tracks/{id}/snapshots/preview: + get: + summary: 'Preview a virtual track snapshot' + operationId: 'release-tracks-virtual-snapshot-preview' + description: | + Compute what a virtual snapshot would contain without persisting it. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Virtual snapshot preview generated' + '501': + description: 'Not yet implemented' + + # ============================================================================= + # Snapshot-specific operations + # ============================================================================= + /api/release-tracks/{id}/snapshots/{modified}: + get: + summary: 'Get a specific snapshot by modified timestamp' + operationId: 'release-tracks-snapshot-get' + description: | + Retrieve a historical snapshot identified by its modified timestamp. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: modified + in: path + required: true + description: 'ISO 8601 timestamp' + schema: + type: string + - name: include + in: query + schema: + type: string + enum: + - members + - staged + - candidates + - all + default: members + - name: format + in: query + schema: + type: string + enum: + - snapshot + - bundle + - workbench + default: snapshot + responses: + '200': + description: 'Snapshot retrieved successfully' + '404': + description: 'Snapshot not found' + + delete: + summary: 'Delete a specific snapshot' + operationId: 'release-tracks-snapshot-delete' + description: | + Delete a snapshot by its modified timestamp. + Cannot delete tagged snapshots. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: modified + in: path + required: true + schema: + type: string + responses: + '204': + description: 'Snapshot deleted successfully' + '400': + description: 'Cannot delete tagged snapshot' + '404': + description: 'Snapshot not found' + + /api/release-tracks/{id}/snapshots/{modified}/meta: + post: + summary: 'Update metadata on a specific snapshot' + operationId: 'release-tracks-update-meta-by-modified' + description: | + Update metadata on a historical snapshot. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: modified + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Metadata updated successfully' + + /api/release-tracks/{id}/snapshots/{modified}/contents: + post: + summary: 'Update contents on a specific snapshot' + operationId: 'release-tracks-update-contents-by-modified' + description: | + Update member contents on a historical snapshot. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: modified + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Contents updated successfully' + + /api/release-tracks/{id}/snapshots/{modified}/clone: + post: + summary: 'Clone a specific snapshot into a new release track' + operationId: 'release-tracks-clone-by-modified' + description: | + Create a new release track by cloning a historical snapshot. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: modified + in: path + required: true + schema: + type: string + responses: + '201': + description: 'Release track cloned successfully' + + /api/release-tracks/{id}/snapshots/{modified}/bump: + post: + summary: 'Tag a specific snapshot' + operationId: 'release-tracks-bump-by-modified' + description: | + Tag a historical snapshot with a version number. + Request body validated via Zod in controller. + tags: + - 'Release Tracks' + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: modified + in: path + required: true + schema: + type: string + responses: + '200': + description: 'Snapshot tagged successfully' + '501': + description: 'Not yet implemented' From e4dcf5cf600e14e6be1cc9636db981d33bc1692c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:21:41 -0500 Subject: [PATCH 190/370] feat(release-tracks): implement bump/tag/versioning operations - new file (lib/version-utils): has functions for parsing, comparing, validating, and calculating x_mitre_version - new file (versioning-service): sub-service for managing the bump/tag lifecycle for release track snapshots - mod file (release-track-dynamic.repository): tagSnapshotInPlace now accepts additional options to enable atomic staged-to-members promotion during tagging - mod file (release-tracks-service): implement bumpLatest, bumpByModified, and previewBump This concludes phase 4 of the release track service layer implementation. --- app/lib/release-tracks/version-utils.js | 112 ++++++++ .../release-track-dynamic.repository.js | 10 +- .../release-tracks/release-tracks-service.js | 17 +- .../release-tracks/versioning-service.js | 261 ++++++++++++++++++ 4 files changed, 391 insertions(+), 9 deletions(-) create mode 100644 app/lib/release-tracks/version-utils.js create mode 100644 app/services/release-tracks/versioning-service.js diff --git a/app/lib/release-tracks/version-utils.js b/app/lib/release-tracks/version-utils.js new file mode 100644 index 00000000..917b5df2 --- /dev/null +++ b/app/lib/release-tracks/version-utils.js @@ -0,0 +1,112 @@ +'use strict'; + +// ============================================================================= +// Version Utilities +// +// Parsing, comparison, calculation, and validation for MAJOR.MINOR version +// strings used by release track tagging. +// +// ATT&CK release tracks use a two-part versioning scheme (MAJOR.MINOR), +// not three-part semver. See docs/COLLECTIONS_V2/03_VERSIONING.md. +// ============================================================================= + +const { InvalidVersionError } = require('../../exceptions'); + +const VERSION_PATTERN = /^\d+\.\d+$/; + +/** + * Parse a version string into its numeric components. + * + * @param {string} str - Version string in "MAJOR.MINOR" format + * @returns {{ major: number, minor: number }} + * @throws {InvalidVersionError} If the string is not a valid version + */ +exports.parseVersion = function parseVersion(str) { + if (!str || !VERSION_PATTERN.test(str)) { + throw new InvalidVersionError(`Invalid version format: "${str}" (expected MAJOR.MINOR)`); + } + const [major, minor] = str.split('.').map(Number); + return { major, minor }; +}; + +/** + * Compare two version strings. + * + * @param {string} a - First version + * @param {string} b - Second version + * @returns {number} -1 if a < b, 0 if a === b, 1 if a > b + */ +exports.compareVersions = function compareVersions(a, b) { + const va = exports.parseVersion(a); + const vb = exports.parseVersion(b); + + if (va.major !== vb.major) return va.major < vb.major ? -1 : 1; + if (va.minor !== vb.minor) return va.minor < vb.minor ? -1 : 1; + return 0; +}; + +/** + * Calculate the next version based on version history and bump type. + * + * If an explicit version is provided, it is returned as-is (validation + * is handled separately by validateVersionProgression). + * + * If the version history is empty, the first version defaults to "1.0". + * + * @param {Array<{ version: string }>} versionHistory - Existing version history entries + * @param {string} [bumpType='minor'] - 'major' or 'minor' + * @param {string} [explicitVersion] - Explicit version override + * @returns {string} The calculated version string + */ +exports.calculateNextVersion = function calculateNextVersion( + versionHistory, + bumpType, + explicitVersion, +) { + if (explicitVersion) { + // Validate format only; monotonicity is checked by validateVersionProgression + exports.parseVersion(explicitVersion); + return explicitVersion; + } + + if (!versionHistory || versionHistory.length === 0) { + return '1.0'; + } + + // Find the highest existing version (history may not be sorted) + let highest = null; + for (const entry of versionHistory) { + if (!highest || exports.compareVersions(entry.version, highest) > 0) { + highest = entry.version; + } + } + + const { major, minor } = exports.parseVersion(highest); + const type = bumpType || 'minor'; + + return type === 'major' ? `${major + 1}.0` : `${major}.${minor + 1}`; +}; + +/** + * Validate that a new version is strictly greater than all existing versions. + * + * @param {string} newVersion - The version to validate + * @param {Array<{ version: string }>} versionHistory - Existing version history entries + * @throws {InvalidVersionError} If the version is not greater than all existing versions + */ +exports.validateVersionProgression = function validateVersionProgression( + newVersion, + versionHistory, +) { + if (!versionHistory || versionHistory.length === 0) { + return; // No history — any valid version is acceptable + } + + for (const entry of versionHistory) { + if (exports.compareVersions(newVersion, entry.version) <= 0) { + throw new InvalidVersionError( + `Version "${newVersion}" must be greater than existing version "${entry.version}"`, + ); + } + } +}; diff --git a/app/repository/release-tracks/release-track-dynamic.repository.js b/app/repository/release-tracks/release-track-dynamic.repository.js index 8457c079..e34be760 100644 --- a/app/repository/release-tracks/release-track-dynamic.repository.js +++ b/app/repository/release-tracks/release-track-dynamic.repository.js @@ -125,6 +125,14 @@ class ReleaseTrackDynamicRepository { async tagSnapshotInPlace(trackId, modified, versionData) { try { const Model = this._getModel(trackId); + + const setOps = { version: versionData.version }; + + // Merge additional atomic operations (e.g., staged → members promotion) + if (versionData.additionalOps) { + Object.assign(setOps, versionData.additionalOps); + } + const result = await Model.findOneAndUpdate( { id: trackId, @@ -132,7 +140,7 @@ class ReleaseTrackDynamicRepository { version: null, // Guard: only tag untagged snapshots }, { - $set: { version: versionData.version }, + $set: setOps, $push: { version_history: versionData.versionHistoryEntry }, }, { diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index c4ba4f77..678b228e 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -11,7 +11,7 @@ // Phase 1: Track management, snapshot CRUD, config → snapshot-service // Phase 2: Candidates, staged, object versions → standard-track-service // Phase 3: Auto-promotion, workflow → workflow-service -// Phase 4: Bump/tag, versioning → versioning-service (TODO) +// Phase 4: Bump/tag, versioning → versioning-service // Phase 5: Virtual track composition → virtual-track-service (TODO) // Phase 6: Export, ephemeral → export-service, ephemeral-service (TODO) // ============================================================================= @@ -19,6 +19,7 @@ const { NotImplementedError } = require('../../exceptions'); const snapshotService = require('./snapshot-service'); const standardTrackService = require('./standard-track-service'); +const versioningService = require('./versioning-service'); const MODULE = 'release-tracks-service'; @@ -145,19 +146,19 @@ exports.demoteStaged = function demoteStaged(trackId, objectRefs, userId) { }; // ----------------------------------------------------------------------------- -// Versioning (Phase 4 → versioning-service, TODO) +// Versioning (Phase 4 → versioning-service) // ----------------------------------------------------------------------------- -exports.bumpLatest = async function bumpLatest(_trackId, _options) { - notImplemented('bumpLatest'); +exports.bumpLatest = function bumpLatest(trackId, options) { + return versioningService.bumpLatest(trackId, options); }; -exports.bumpByModified = async function bumpByModified(_trackId, _modified, _options) { - notImplemented('bumpByModified'); +exports.bumpByModified = function bumpByModified(trackId, modified, options) { + return versioningService.bumpByModified(trackId, modified, options); }; -exports.previewBump = async function previewBump(_trackId, _format) { - notImplemented('previewBump'); +exports.previewBump = function previewBump(trackId, format) { + return versioningService.previewBump(trackId, format); }; // ----------------------------------------------------------------------------- diff --git a/app/services/release-tracks/versioning-service.js b/app/services/release-tracks/versioning-service.js new file mode 100644 index 00000000..5c51a3ca --- /dev/null +++ b/app/services/release-tracks/versioning-service.js @@ -0,0 +1,261 @@ +'use strict'; + +// ============================================================================= +// Versioning Service +// +// Manages the bump/tag lifecycle for release track snapshots: +// - Calculate and assign version numbers (MAJOR.MINOR) +// - Promote staged entries to members atomically with tagging +// - Preview upcoming bumps without persisting +// +// Tagging is the ONLY in-place mutation on a snapshot. All other changes +// produce new snapshot clones via snapshot-service. +// +// See docs/COLLECTIONS_V2/03_VERSIONING.md for versioning rules. +// ============================================================================= + +const snapshotService = require('./snapshot-service'); +const dynamicRepo = require('../../repository/release-tracks/release-track-dynamic.repository'); +const registryRepo = require('../../repository/release-tracks/release-track-registry.repository'); +const versionUtils = require('../../lib/release-tracks/version-utils'); +const conflictResolution = require('../../lib/release-tracks/conflict-resolution'); +const logger = require('../../lib/logger'); +const { AlreadyReleasedError } = require('../../exceptions'); + +// ============================================================================= +// Internal helpers +// ============================================================================= + +/** + * Core bump logic shared by bumpLatest and bumpByModified. + * + * @param {string} trackId + * @param {Object} snapshot - The snapshot to tag + * @param {Object} options - { type?, version?, dry_run?, userAccountId } + * @returns {Promise} The tagged snapshot (or preview if dry_run) + */ +async function _doBump(trackId, snapshot, options) { + // Guard: cannot re-tag an already-tagged snapshot + if (snapshot.version != null) { + throw new AlreadyReleasedError(snapshot.version); + } + + const versionHistory = snapshot.version_history || []; + + // Calculate version + const version = versionUtils.calculateNextVersion( + versionHistory, + options.type, + options.version, + ); + + // Validate monotonic progression + versionUtils.validateVersionProgression(version, versionHistory); + + // Promote staged → members (standard tracks only) + const staged = snapshot.staged || []; + const existingMembers = snapshot.members || []; + let mergedMembers = existingMembers; + let promotedCount = 0; + + if (staged.length > 0) { + // Convert staged entries to member entries (strip staged-specific fields) + const stagedAsMembers = staged.map((s) => ({ + object_ref: s.object_ref, + object_modified: s.object_modified, + })); + + const policy = + (snapshot.config && + snapshot.config.promotion_conflicts && + snapshot.config.promotion_conflicts.staged_to_members) || + 'abort'; + + const { merged } = conflictResolution.applyConflictPolicy( + existingMembers, + stagedAsMembers, + policy, + ); + + mergedMembers = merged; + promotedCount = staged.length; + } + + const now = new Date(); + + // Build version history entry + const versionHistoryEntry = { + version, + tagged_at: now, + tagged_by: options.userAccountId || 'system', + snapshot_id: snapshot.modified, + summary: { + members_count: mergedMembers.length, + promoted_count: promotedCount, + staged_count: staged.length, + candidate_count: (snapshot.candidates || []).length, + }, + }; + + // Dry-run: return preview without persisting + if (options.dry_run) { + return { + dry_run: true, + track_id: trackId, + snapshot_modified: snapshot.modified, + version, + staged_to_promote: staged.length, + members_after: mergedMembers.length, + version_history_entry: versionHistoryEntry, + }; + } + + // Build additional atomic ops for the tag update + const additionalOps = {}; + if (staged.length > 0) { + additionalOps.members = mergedMembers; + additionalOps.staged = []; + } + + // Atomic tag + promotion + const tagged = await dynamicRepo.tagSnapshotInPlace(trackId, snapshot.modified, { + version, + versionHistoryEntry, + additionalOps: Object.keys(additionalOps).length > 0 ? additionalOps : undefined, + }); + + if (!tagged) { + // Race condition: snapshot was already tagged between our read and update + throw new AlreadyReleasedError('(concurrent tag)'); + } + + // Update registry counters + await registryRepo.updateByTrackId(trackId, { + latest_tagged_version: version, + tagged_release_count: (versionHistory.length + 1), + updated_at: now, + }); + + logger.verbose( + `VersioningService: Tagged track "${trackId}" as v${version} ` + + `(promoted ${promotedCount} staged → members)`, + ); + + return tagged; +} + +// ============================================================================= +// Public API +// ============================================================================= + +/** + * Tag the latest snapshot of a track as a versioned release. + * + * - Calculates the next version (or uses explicit version from options) + * - Promotes all staged entries to members atomically + * - Records the version in version_history + * - Updates registry counters + * + * @param {string} trackId + * @param {Object} options - { type?: 'major'|'minor', version?: string, dry_run?: boolean, userAccountId?: string } + * @returns {Promise} The tagged snapshot (or preview object if dry_run) + */ +exports.bumpLatest = async function bumpLatest(trackId, options = {}) { + const snapshot = await snapshotService.getLatestSnapshot(trackId); + return _doBump(trackId, snapshot, options); +}; + +/** + * Tag a specific snapshot (by modified timestamp) as a versioned release. + * + * Same semantics as bumpLatest but targets a specific snapshot. + * + * @param {string} trackId + * @param {string|Date} modified - The snapshot's modified timestamp + * @param {Object} options - { type?: 'major'|'minor', version?: string, dry_run?: boolean, userAccountId?: string } + * @returns {Promise} The tagged snapshot (or preview object if dry_run) + */ +exports.bumpByModified = async function bumpByModified(trackId, modified, options = {}) { + const snapshot = await snapshotService.getSnapshotByModified(trackId, modified); + return _doBump(trackId, snapshot, options); +}; + +/** + * Preview what a bump on the latest snapshot would produce without persisting. + * + * Returns the calculated version, staged-to-members diff, and summary stats. + * + * @param {string} trackId + * @param {string} [_format] - Reserved for future export format support + * @returns {Promise} Preview object + */ +// eslint-disable-next-line no-unused-vars +exports.previewBump = async function previewBump(trackId, _format) { + const snapshot = await snapshotService.getLatestSnapshot(trackId); + + const versionHistory = snapshot.version_history || []; + const staged = snapshot.staged || []; + const existingMembers = snapshot.members || []; + + // Calculate what the next version would be (default minor bump) + const isAlreadyTagged = snapshot.version != null; + const nextMinor = isAlreadyTagged + ? null + : versionUtils.calculateNextVersion(versionHistory, 'minor'); + const nextMajor = isAlreadyTagged + ? null + : versionUtils.calculateNextVersion(versionHistory, 'major'); + + // Preview staged → members merge + let mergedMembersCount = existingMembers.length; + if (staged.length > 0 && !isAlreadyTagged) { + const stagedAsMembers = staged.map((s) => ({ + object_ref: s.object_ref, + object_modified: s.object_modified, + })); + + const policy = + (snapshot.config && + snapshot.config.promotion_conflicts && + snapshot.config.promotion_conflicts.staged_to_members) || + 'abort'; + + try { + const { merged } = conflictResolution.applyConflictPolicy( + existingMembers, + stagedAsMembers, + policy, + ); + mergedMembersCount = merged.length; + } catch (err) { + // If policy is 'abort' and conflicts exist, report it in the preview + return { + track_id: trackId, + snapshot_modified: snapshot.modified, + is_already_tagged: isAlreadyTagged, + current_version: snapshot.version, + next_version_minor: nextMinor, + next_version_major: nextMajor, + staged_count: staged.length, + members_count: existingMembers.length, + candidates_count: (snapshot.candidates || []).length, + conflict_error: err.message, + }; + } + } + + return { + track_id: trackId, + snapshot_modified: snapshot.modified, + is_already_tagged: isAlreadyTagged, + current_version: snapshot.version, + next_version_minor: nextMinor, + next_version_major: nextMajor, + staged_count: staged.length, + staged_to_promote: isAlreadyTagged ? 0 : staged.length, + members_count: existingMembers.length, + members_after_promotion: isAlreadyTagged ? existingMembers.length : mergedMembersCount, + candidates_count: (snapshot.candidates || []).length, + version_history: versionHistory, + }; +}; From e8a322feddaf8e4d6b1fc976168be5400fb1c130 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 13:42:54 -0500 Subject: [PATCH 191/370] feat(release-tracks): implement virtual tracks - new file (deduplication-strategies): engine for handling dupe objects detected during virtual track composition - new file (virtual-track-service): sub-service for managing virtual release track operations - mod file (release-tracks-service): implement updateComposition, createVirtualSnapshot, and previewVirtualSnapshot This concludes phase 5 of the release track service layer implementation. --- .../deduplication-strategies.js | 193 ++++++++ .../release-tracks/release-tracks-service.js | 17 +- .../release-tracks/virtual-track-service.js | 420 ++++++++++++++++++ 3 files changed, 622 insertions(+), 8 deletions(-) create mode 100644 app/lib/release-tracks/deduplication-strategies.js create mode 100644 app/services/release-tracks/virtual-track-service.js diff --git a/app/lib/release-tracks/deduplication-strategies.js b/app/lib/release-tracks/deduplication-strategies.js new file mode 100644 index 00000000..de9090dc --- /dev/null +++ b/app/lib/release-tracks/deduplication-strategies.js @@ -0,0 +1,193 @@ +'use strict'; + +// ============================================================================= +// Deduplication Strategies +// +// Resolves duplicate objects when multiple component tracks contribute the same +// STIX object (same object_ref) to a virtual track snapshot. +// +// Strategies: +// prioritize_latest_object – Keep the version with the newest object_modified +// prioritize_latest_snapshot – Keep the version from the most recently modified component snapshot +// prioritize_higher_priority – Keep the version from the higher-priority component (lower number) +// quarantine – Send all conflicting versions to quarantine for manual review +// +// Input members are annotated with source metadata: +// _source_track_id, _source_track_name, _source_snapshot_modified, +// _source_snapshot_version, _source_priority +// ============================================================================= + +/** + * Deduplicate members collected from multiple component tracks. + * + * @param {Array} allMembers - Annotated member entries from all components. + * Each entry: { object_ref, object_modified, _source_track_id, _source_track_name, + * _source_snapshot_modified, _source_snapshot_version, _source_priority } + * @param {string} strategy - One of the four deduplication strategies + * @returns {{ members: Array, quarantined: Array, report: Object }} + */ +exports.deduplicate = function deduplicate(allMembers, strategy) { + // Group entries by object_ref to identify duplicates + const groups = new Map(); + for (const entry of allMembers) { + const key = entry.object_ref; + if (!groups.has(key)) { + groups.set(key, []); + } + groups.get(key).push(entry); + } + + const members = []; + const quarantined = []; + const conflictsResolved = []; + + for (const [objectRef, entries] of groups) { + if (entries.length === 1) { + // No conflict — single source + members.push(_stripSourceMeta(entries[0])); + continue; + } + + // Conflict: same object_ref from multiple component tracks + switch (strategy) { + case 'prioritize_latest_object': + _resolveByLatestObject(objectRef, entries, members, conflictsResolved); + break; + + case 'prioritize_latest_snapshot': + _resolveByLatestSnapshot(objectRef, entries, members, conflictsResolved); + break; + + case 'prioritize_higher_priority': + _resolveByHigherPriority(objectRef, entries, members, conflictsResolved); + break; + + case 'quarantine': + _resolveByQuarantine(objectRef, entries, quarantined, conflictsResolved); + break; + + default: + throw new Error(`Unknown deduplication strategy: ${strategy}`); + } + } + + const report = { + total_objects_before: allMembers.length, + total_objects_after: members.length, + duplicates_found: conflictsResolved.length, + conflicts_resolved: conflictsResolved, + }; + + return { members, quarantined, report }; +}; + +// ============================================================================= +// Strategy implementations +// ============================================================================= + +/** + * Keep the entry with the most recent object_modified timestamp. + */ +function _resolveByLatestObject(objectRef, entries, members, conflictsResolved) { + let winner = entries[0]; + for (let i = 1; i < entries.length; i++) { + if ( + new Date(entries[i].object_modified).getTime() > new Date(winner.object_modified).getTime() + ) { + winner = entries[i]; + } + } + + members.push(_stripSourceMeta(winner)); + conflictsResolved.push({ + object_ref: objectRef, + strategy: 'prioritize_latest_object', + winner_source: winner._source_track_id, + winner_modified: winner.object_modified, + candidates_count: entries.length, + }); +} + +/** + * Keep the entry from the component track whose resolved snapshot has the + * most recent modified timestamp. + */ +function _resolveByLatestSnapshot(objectRef, entries, members, conflictsResolved) { + let winner = entries[0]; + for (let i = 1; i < entries.length; i++) { + const entrySnapshotTime = new Date(entries[i]._source_snapshot_modified).getTime(); + const winnerSnapshotTime = new Date(winner._source_snapshot_modified).getTime(); + if (entrySnapshotTime > winnerSnapshotTime) { + winner = entries[i]; + } + } + + members.push(_stripSourceMeta(winner)); + conflictsResolved.push({ + object_ref: objectRef, + strategy: 'prioritize_latest_snapshot', + winner_source: winner._source_track_id, + winner_snapshot_modified: winner._source_snapshot_modified, + candidates_count: entries.length, + }); +} + +/** + * Keep the entry from the component track with the highest priority + * (lowest priority number). + */ +function _resolveByHigherPriority(objectRef, entries, members, conflictsResolved) { + let winner = entries[0]; + for (let i = 1; i < entries.length; i++) { + if (entries[i]._source_priority < winner._source_priority) { + winner = entries[i]; + } + } + + members.push(_stripSourceMeta(winner)); + conflictsResolved.push({ + object_ref: objectRef, + strategy: 'prioritize_higher_priority', + winner_source: winner._source_track_id, + winner_priority: winner._source_priority, + candidates_count: entries.length, + }); +} + +/** + * Send all conflicting versions to quarantine for manual resolution. + * No entry is added to members for this object_ref. + */ +function _resolveByQuarantine(objectRef, entries, quarantined, conflictsResolved) { + for (const entry of entries) { + quarantined.push({ + object_ref: entry.object_ref, + object_modified: entry.object_modified, + source_track_id: entry._source_track_id, + source_track_name: entry._source_track_name, + source_snapshot_version: entry._source_snapshot_version, + conflict_reason: 'duplicate_object', + }); + } + + conflictsResolved.push({ + object_ref: objectRef, + strategy: 'quarantine', + quarantined_count: entries.length, + }); +} + +// ============================================================================= +// Helpers +// ============================================================================= + +/** + * Strip internal source-tracking metadata from an entry, returning a clean + * member entry suitable for persistence. + */ +function _stripSourceMeta(entry) { + return { + object_ref: entry.object_ref, + object_modified: entry.object_modified, + }; +} diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index 678b228e..c20304d1 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -12,7 +12,7 @@ // Phase 2: Candidates, staged, object versions → standard-track-service // Phase 3: Auto-promotion, workflow → workflow-service // Phase 4: Bump/tag, versioning → versioning-service -// Phase 5: Virtual track composition → virtual-track-service (TODO) +// Phase 5: Virtual track composition → virtual-track-service // Phase 6: Export, ephemeral → export-service, ephemeral-service (TODO) // ============================================================================= @@ -20,6 +20,7 @@ const { NotImplementedError } = require('../../exceptions'); const snapshotService = require('./snapshot-service'); const standardTrackService = require('./standard-track-service'); const versioningService = require('./versioning-service'); +const virtualTrackService = require('./virtual-track-service'); const MODULE = 'release-tracks-service'; @@ -174,19 +175,19 @@ exports.updateConfig = function updateConfig(trackId, config, userId) { }; // ----------------------------------------------------------------------------- -// Virtual tracks (Phase 5 → virtual-track-service, TODO) +// Virtual tracks (Phase 5 → virtual-track-service) // ----------------------------------------------------------------------------- -exports.updateComposition = async function updateComposition(_trackId, _composition, _userId) { - notImplemented('updateComposition'); +exports.updateComposition = function updateComposition(trackId, composition, userId) { + return virtualTrackService.updateComposition(trackId, composition, userId); }; -exports.createVirtualSnapshot = async function createVirtualSnapshot(_trackId, _options) { - notImplemented('createVirtualSnapshot'); +exports.createVirtualSnapshot = function createVirtualSnapshot(trackId, options) { + return virtualTrackService.createVirtualSnapshot(trackId, options); }; -exports.previewVirtualSnapshot = async function previewVirtualSnapshot(_trackId) { - notImplemented('previewVirtualSnapshot'); +exports.previewVirtualSnapshot = function previewVirtualSnapshot(trackId) { + return virtualTrackService.previewVirtualSnapshot(trackId); }; // ----------------------------------------------------------------------------- diff --git a/app/services/release-tracks/virtual-track-service.js b/app/services/release-tracks/virtual-track-service.js new file mode 100644 index 00000000..3669b6db --- /dev/null +++ b/app/services/release-tracks/virtual-track-service.js @@ -0,0 +1,420 @@ +'use strict'; + +// ============================================================================= +// Virtual Track Service +// +// Manages virtual release track operations: composition configuration and +// snapshot creation via resolution of component tracks. +// +// Virtual tracks aggregate content from multiple standard tracks by: +// 1. Resolving each component track to a specific tagged snapshot +// 2. Collecting members from each resolved snapshot +// 3. Applying per-component filters (object_types) +// 4. Deduplicating across all components +// 5. Persisting the result as a new draft snapshot +// +// See docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md for full specification. +// ============================================================================= + +const snapshotService = require('./snapshot-service'); +const dynamicRepo = require('../../repository/release-tracks/release-track-dynamic.repository'); +const registryRepo = require('../../repository/release-tracks/release-track-registry.repository'); +const deduplicationStrategies = require('../../lib/release-tracks/deduplication-strategies'); +const logger = require('../../lib/logger'); +const { + BadRequestError, + TrackNotFoundError, + NoTaggedSnapshotsError, + InvalidComponentTypeError, +} = require('../../exceptions'); + +// ============================================================================= +// Internal helpers +// ============================================================================= + +/** + * Validate that the snapshot belongs to a virtual track. + * @param {Object} snapshot + * @throws {BadRequestError} + */ +function assertVirtualTrack(snapshot) { + if (snapshot.type !== 'virtual') { + throw new BadRequestError({ + message: 'This operation is only available for virtual release tracks', + details: `Track ${snapshot.id} is a ${snapshot.type} track`, + }); + } +} + +/** + * Validate that all component tracks exist, are standard tracks, and have + * no duplicate track_ids or priority values. + * + * @param {Array} componentTracks - The composition.component_tracks array + * @returns {Promise>} Map of track_id → registry entry + */ +async function validateComponentTracks(componentTracks) { + if (!componentTracks || componentTracks.length === 0) { + throw new BadRequestError({ + message: 'Composition must include at least one component track', + }); + } + + // Check for duplicate track_ids + const trackIds = componentTracks.map((c) => c.track_id); + const uniqueTrackIds = new Set(trackIds); + if (uniqueTrackIds.size !== trackIds.length) { + throw new BadRequestError({ + message: 'Duplicate track_id values found in component_tracks', + details: 'Each component track must reference a unique track', + }); + } + + // Check for duplicate priorities + const priorities = componentTracks.map((c) => c.priority); + const uniquePriorities = new Set(priorities); + if (uniquePriorities.size !== priorities.length) { + throw new BadRequestError({ + message: 'Duplicate priority values found in component_tracks', + details: 'Each component track must have a unique priority value', + }); + } + + // Validate each component exists and is a standard track + const registryMap = new Map(); + for (const component of componentTracks) { + const registry = await registryRepo.findByTrackId(component.track_id); + if (!registry) { + throw new TrackNotFoundError(component.track_id); + } + if (registry.type === 'virtual') { + throw new InvalidComponentTypeError(component.track_id); + } + registryMap.set(component.track_id, registry); + } + + return registryMap; +} + +/** + * Resolve a component track to a specific tagged snapshot based on its + * resolution strategy. + * + * @param {Object} component - A component_tracks entry + * @returns {Promise} The resolved snapshot document + * @throws {NoTaggedSnapshotsError} If no suitable tagged snapshot is found + */ +async function resolveComponentSnapshot(component) { + let snapshot; + + switch (component.resolution_strategy) { + case 'latest_tagged': + snapshot = await dynamicRepo.getLatestTaggedSnapshot(component.track_id); + break; + + case 'specific_version': + snapshot = await dynamicRepo.getSnapshotByVersion(component.track_id, component.version); + break; + + case 'specific_snapshot': + snapshot = await dynamicRepo.getSnapshotByModified(component.track_id, component.snapshot); + break; + + default: + throw new BadRequestError({ + message: `Unknown resolution strategy: ${component.resolution_strategy}`, + }); + } + + if (!snapshot) { + throw new NoTaggedSnapshotsError(component.track_id); + } + + // For specific_snapshot strategy, the snapshot may be a draft — validate it's tagged + if (snapshot.version == null) { + throw new NoTaggedSnapshotsError(component.track_id); + } + + return snapshot; +} + +/** + * Apply object_types filter to a list of member entries. + * Filters by extracting the STIX type prefix from the object_ref + * (e.g., "attack-pattern" from "attack-pattern--uuid"). + * + * @param {Array} members - Member entries with object_ref + * @param {Object} [filters] - { object_types?: string[], domains?: string[] } + * @returns {Array} Filtered members + */ +function applyFilters(members, filters) { + if (!filters) return members; + + let filtered = members; + + if (filters.object_types && filters.object_types.length > 0) { + const allowedTypes = new Set(filters.object_types); + filtered = filtered.filter((m) => { + const stixType = m.object_ref.split('--')[0]; + return allowedTypes.has(stixType); + }); + } + + // Note: domains filtering requires fetching full STIX objects, which is + // deferred to Phase 6 (export-service). For now, domains filter is a no-op + // logged as a warning. + if (filters.domains && filters.domains.length > 0) { + logger.warn( + 'VirtualTrackService: domains filter is not yet implemented (requires Phase 6 export infrastructure)', + ); + } + + return filtered; +} + +/** + * Core composition resolution logic shared by createVirtualSnapshot and + * previewVirtualSnapshot. + * + * @param {Object} snapshot - The current virtual track snapshot + * @param {Map} registryMap - track_id → registry entry + * @returns {Promise} Resolution result with members, quarantined, and metadata + */ +async function resolveComposition(snapshot, registryMap) { + const composition = snapshot.composition; + const componentTracks = composition.component_tracks || []; + const strategy = + (composition.deduplication && composition.deduplication.strategy) || 'prioritize_latest_object'; + + const now = new Date(); + const componentSnapshotsMeta = []; + const allAnnotatedMembers = []; + + // Resolve each component track in parallel + const resolutions = await Promise.all( + componentTracks.map((component) => resolveComponentSnapshot(component)), + ); + + for (let i = 0; i < componentTracks.length; i++) { + const component = componentTracks[i]; + const resolvedSnapshot = resolutions[i]; + const registry = registryMap.get(component.track_id); + + // Extract members from the resolved snapshot + const sourceMembers = resolvedSnapshot.members || []; + const totalObjectsInSource = sourceMembers.length; + + // Apply filters + const filteredMembers = applyFilters(sourceMembers, component.filters); + const objectsAfterFilter = filteredMembers.length; + + // Annotate each member with source metadata for deduplication + for (const member of filteredMembers) { + allAnnotatedMembers.push({ + object_ref: member.object_ref, + object_modified: member.object_modified, + _source_track_id: component.track_id, + _source_track_name: registry.name, + _source_snapshot_modified: resolvedSnapshot.modified, + _source_snapshot_version: resolvedSnapshot.version, + _source_priority: component.priority, + }); + } + + // Build component resolution metadata + componentSnapshotsMeta.push({ + track_id: component.track_id, + track_name: registry.name, + track_type: registry.type, + resolved_snapshot_id: resolvedSnapshot.modified, + resolved_version: resolvedSnapshot.version, + strategy_used: component.resolution_strategy, + filters_applied: component.filters || undefined, + total_objects_in_source: totalObjectsInSource, + objects_after_filter: objectsAfterFilter, + objects_contributed: 0, // Updated after deduplication + }); + } + + // Deduplicate across all components + const { members, quarantined, report } = deduplicationStrategies.deduplicate( + allAnnotatedMembers, + strategy, + ); + + // Update objects_contributed per component by counting how many of each + // component's members survived deduplication + const survivorSources = new Map(); + for (const annotated of allAnnotatedMembers) { + // Check if this specific entry survived deduplication + const survived = members.some( + (m) => + m.object_ref === annotated.object_ref && + new Date(m.object_modified).getTime() === new Date(annotated.object_modified).getTime(), + ); + if (survived) { + const count = survivorSources.get(annotated._source_track_id) || 0; + survivorSources.set(annotated._source_track_id, count + 1); + } + } + + for (const meta of componentSnapshotsMeta) { + meta.objects_contributed = survivorSources.get(meta.track_id) || 0; + } + + // Build composition_resolution + const compositionResolution = { + resolved_at: now, + component_snapshots: componentSnapshotsMeta, + deduplication: report, + summary: { + total_objects: members.length, + quarantined_objects: quarantined.length, + }, + }; + + return { members, quarantined, compositionResolution }; +} + +// ============================================================================= +// Public API +// ============================================================================= + +/** + * Update the composition rules for a virtual track. + * + * Validates all component tracks exist and are standard tracks, then clones + * the latest snapshot with the updated composition. + * + * @param {string} trackId + * @param {Object} composition - The new composition configuration + * @param {string} [userId] + * @returns {Promise} The new snapshot + */ +// eslint-disable-next-line no-unused-vars +exports.updateComposition = async function updateComposition(trackId, composition, userId) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertVirtualTrack(source); + + // Validate all component tracks + await validateComponentTracks(composition.component_tracks); + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, { composition }); + + logger.verbose( + `VirtualTrackService: Updated composition for track "${trackId}" ` + + `(${composition.component_tracks.length} component track(s))`, + ); + return snapshot; +}; + +/** + * Create a new virtual snapshot by resolving the composition rules. + * + * For each component track: + * 1. Resolve to a tagged snapshot via the configured strategy + * 2. Extract and filter members + * Then deduplicate across all components and persist a new draft snapshot. + * + * @param {string} trackId + * @param {Object} [options] - { description?, userAccountId? } + * @returns {Promise} The new snapshot with composition_resolution metadata + */ +exports.createVirtualSnapshot = async function createVirtualSnapshot(trackId, options = {}) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertVirtualTrack(source); + + const composition = source.composition; + if (!composition || !composition.component_tracks || composition.component_tracks.length === 0) { + throw new BadRequestError({ + message: 'Cannot create virtual snapshot: no component tracks configured', + details: 'Update the composition before creating a snapshot', + }); + } + + // Validate component tracks + const registryMap = await validateComponentTracks(composition.component_tracks); + + // Resolve composition + const { members, quarantined, compositionResolution } = await resolveComposition( + source, + registryMap, + ); + + // Build overrides for the new snapshot + const overrides = { + members, + quarantine: quarantined, + composition_resolution: compositionResolution, + }; + + if (options.description !== undefined) { + overrides.description = options.description; + } + + const snapshot = await snapshotService.cloneSnapshot(trackId, source, overrides); + + logger.verbose( + `VirtualTrackService: Created virtual snapshot for track "${trackId}" ` + + `(${members.length} members, ${quarantined.length} quarantined)`, + ); + return snapshot; +}; + +/** + * Preview what a virtual snapshot would contain without persisting. + * + * Runs the same resolution and deduplication logic as createVirtualSnapshot + * but returns the results without saving a new snapshot. + * + * @param {string} trackId + * @returns {Promise} Preview object with resolution details + */ +exports.previewVirtualSnapshot = async function previewVirtualSnapshot(trackId) { + const source = await snapshotService.getLatestSnapshot(trackId); + assertVirtualTrack(source); + + const composition = source.composition; + if (!composition || !composition.component_tracks || composition.component_tracks.length === 0) { + throw new BadRequestError({ + message: 'Cannot preview virtual snapshot: no component tracks configured', + details: 'Update the composition before previewing a snapshot', + }); + } + + // Validate component tracks + const registryMap = await validateComponentTracks(composition.component_tracks); + + // Resolve composition (same logic, but we don't persist) + const { members, quarantined, compositionResolution } = await resolveComposition( + source, + registryMap, + ); + + // Build comparison to the latest tagged version (if any) + const existingMembers = source.members || []; + const existingMemberRefs = new Set(existingMembers.map((m) => m.object_ref)); + const newMemberRefs = new Set(members.map((m) => m.object_ref)); + + const newObjects = members.filter((m) => !existingMemberRefs.has(m.object_ref)); + const removedObjects = existingMembers.filter((m) => !newMemberRefs.has(m.object_ref)); + const updatedObjects = members.filter((m) => { + const existing = existingMembers.find((e) => e.object_ref === m.object_ref); + if (!existing) return false; + return new Date(m.object_modified).getTime() !== new Date(existing.object_modified).getTime(); + }); + + return { + track_id: trackId, + preview: true, + composition_resolution: compositionResolution, + members_count: members.length, + quarantined_count: quarantined.length, + comparison_to_current: { + current_members_count: existingMembers.length, + new_objects: newObjects.length, + updated_objects: updatedObjects.length, + removed_objects: removedObjects.length, + }, + }; +}; From 6dcc8735ab1b76d3da585c7fb9c4b52b35af7737 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:25:35 -0500 Subject: [PATCH 192/370] fix(release-tracks): restrict dangerous 'Update Contents' endpoint to admins only --- app/routes/release-tracks-routes.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/routes/release-tracks-routes.js b/app/routes/release-tracks-routes.js index 120b804e..17209fb2 100644 --- a/app/routes/release-tracks-routes.js +++ b/app/routes/release-tracks-routes.js @@ -77,11 +77,16 @@ router releaseTracksController.updateMetadataByLatest, ); +/** + * !!IMPORTANT + * The following endpoint is considered dangerous. It is intended for retroactive hotfixes only. Thus, only admins may use it. + * The main workflow for enrolling new member objects into members is through the candidate-staging promotion cycle. + */ router .route('/release-tracks/:id/contents') .post( authn.authenticate, - authz.requireRole(authz.editorOrHigher), + authz.requireRole(authz.admin), releaseTracksController.updateContentsByLatest, ); From ccdf50faab15baf2f3573b6ba37aee95e095e554 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:26:09 -0500 Subject: [PATCH 193/370] style: apply formatting --- app/services/release-tracks/versioning-service.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/services/release-tracks/versioning-service.js b/app/services/release-tracks/versioning-service.js index 5c51a3ca..d1a8b448 100644 --- a/app/services/release-tracks/versioning-service.js +++ b/app/services/release-tracks/versioning-service.js @@ -43,11 +43,7 @@ async function _doBump(trackId, snapshot, options) { const versionHistory = snapshot.version_history || []; // Calculate version - const version = versionUtils.calculateNextVersion( - versionHistory, - options.type, - options.version, - ); + const version = versionUtils.calculateNextVersion(versionHistory, options.type, options.version); // Validate monotonic progression versionUtils.validateVersionProgression(version, versionHistory); @@ -132,7 +128,7 @@ async function _doBump(trackId, snapshot, options) { // Update registry counters await registryRepo.updateByTrackId(trackId, { latest_tagged_version: version, - tagged_release_count: (versionHistory.length + 1), + tagged_release_count: versionHistory.length + 1, updated_at: now, }); From 5688ea375eb5cd5830fbe9b08264ca0f54481884 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 3 Feb 2026 14:57:58 -0500 Subject: [PATCH 194/370] fix(release-tracks): if using abort policy and found conflicts, throw with all of them --- app/lib/release-tracks/conflict-resolution.js | 33 +++++--- .../release-tracks/versioning-service.js | 2 +- docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md | 83 +++++++++++++++++-- 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/app/lib/release-tracks/conflict-resolution.js b/app/lib/release-tracks/conflict-resolution.js index 35a52b9d..5c8b3f9d 100644 --- a/app/lib/release-tracks/conflict-resolution.js +++ b/app/lib/release-tracks/conflict-resolution.js @@ -23,11 +23,12 @@ const { ReleaseConflictError } = require('../../exceptions'); * @param {Array} incomingEntries - Entries being promoted into the tier * @param {string} policy - One of: 'always_overwrite' | 'always_reject' | 'prefer_latest' | 'abort' * @returns {{ merged: Array, rejected: Array }} - * @throws {ReleaseConflictError} If policy is 'abort' and a conflict is detected + * @throws {ReleaseConflictError} If policy is 'abort' and any conflicts are detected */ exports.applyConflictPolicy = function applyConflictPolicy(existingTier, incomingEntries, policy) { const merged = [...existingTier]; const rejected = []; + const conflicts = []; // Collect all conflicts for 'abort' policy for (const incoming of incomingEntries) { const conflictIdx = merged.findIndex((e) => e.object_ref === incoming.object_ref); @@ -61,23 +62,29 @@ exports.applyConflictPolicy = function applyConflictPolicy(existingTier, incomin } case 'abort': - throw new ReleaseConflictError( - `Conflict on ${incoming.object_ref}: abort policy prevents promotion`, - { - conflicts: [ - { - object_ref: incoming.object_ref, - incumbent_version: incumbent.object_modified, - incoming_version: incoming.object_modified, - }, - ], - }, - ); + // Collect all conflicts instead of throwing immediately + conflicts.push({ + object_ref: incoming.object_ref, + incumbent_version: incumbent.object_modified, + incoming_version: incoming.object_modified, + }); + break; default: throw new Error(`Unknown conflict resolution policy: ${policy}`); } } + // If we're using 'abort' policy and found any conflicts, throw with all of them + if (policy === 'abort' && conflicts.length > 0) { + const conflictCount = conflicts.length; + const message = + conflictCount === 1 + ? `Conflict on ${conflicts[0].object_ref}: abort policy prevents promotion` + : `Cannot complete release: ${conflictCount} conflict(s) detected`; + + throw new ReleaseConflictError(message, { conflicts }); + } + return { merged, rejected }; }; diff --git a/app/services/release-tracks/versioning-service.js b/app/services/release-tracks/versioning-service.js index d1a8b448..2b015c93 100644 --- a/app/services/release-tracks/versioning-service.js +++ b/app/services/release-tracks/versioning-service.js @@ -235,7 +235,7 @@ exports.previewBump = async function previewBump(trackId, _format) { staged_count: staged.length, members_count: existingMembers.length, candidates_count: (snapshot.candidates || []).length, - conflict_error: err.message, + conflicts: err.conflicts || [], // Include full conflicts array }; } } diff --git a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md index 5be789b1..2550b51e 100644 --- a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md +++ b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md @@ -342,11 +342,14 @@ Keep whichever version has the newer `modified` timestamp. ##### 4. `abort` (Tagging/Release Operations Only) +[](./05_RELEASE_WORKFLOW.md#4-abort-taggingrelease-operations-only) **Only available for `staged_to_members` during tagging/release operations.** If a conflict occurs during a tagging/release operation (`POST /api/release-tracks/:id/bump`), reject and abort the entire release. The snapshot will NOT be tagged, and no immutable snapshot will be created. -**Example:** +**The error response will include ALL conflicting objects**, not just the first one encountered. This allows editors to see the full scope of conflicts that must be resolved before the release can proceed. + +**Example with single conflict:** ```javascript // Current state: // - members: attack-pattern--T1234, modified: 2024-01-15 @@ -360,28 +363,62 @@ POST /api/release-tracks/release-track--123/bump // ERROR Response: { "error": "ReleaseConflictError", - "message": "Cannot complete release due to promotion conflict", + "message": "Cannot complete release: 1 conflict(s) detected", "conflicts": [ { "object_ref": "attack-pattern--T1234", "incumbent_version": "2024-01-15T10:00:00Z", - "incoming_version": "2024-02-20T10:00:00Z", - "tier": "members" + "incoming_version": "2024-02-20T10:00:00Z" } - ], - "resolution": "Resolve conflicts manually before releasing, or change promotion_conflicts.staged_to_members policy" + ] +} +``` + +**Example with multiple conflicts:** +```javascript +// Current state: +// - members: attack-pattern--T1234, modified: 2024-01-15 +// - members: attack-pattern--T5678, modified: 2024-01-16 +// - staged: attack-pattern--T1234, modified: 2024-02-20 +// - staged: attack-pattern--T5678, modified: 2024-02-21 +// - staged: attack-pattern--T9999, modified: 2024-02-22 (no conflict) + +// Tagging request: +POST /api/release-tracks/release-track--123/bump +{ "type": "minor" } + +// Result with abort - shows ALL conflicts: +// ERROR Response: +{ + "error": "ReleaseConflictError", + "message": "Cannot complete release: 2 conflict(s) detected", + "conflicts": [ + { + "object_ref": "attack-pattern--T1234", + "incumbent_version": "2024-01-15T10:00:00Z", + "incoming_version": "2024-02-20T10:00:00Z" + }, + { + "object_ref": "attack-pattern--T5678", + "incumbent_version": "2024-01-16T10:00:00Z", + "incoming_version": "2024-02-21T10:00:00Z" + } + ] } // State unchanged: // - Snapshot NOT tagged // - No new version history entry // - Objects remain in current tiers +// - Editor must resolve both conflicts before retrying ``` **Use case:** "Never accidentally overwrite released content during a release; require explicit conflict resolution" **Why abort is important:** Once a snapshot is tagged and released, it becomes immutable. The `abort` policy ensures that releases don't inadvertently overwrite existing released content, providing an additional safety guardrail for critical release operations. +**Why report all conflicts:** When multiple conflicts exist, reporting all of them in a single error response allows editors to address all issues at once, rather than discovering them one at a time through repeated release attempts. This significantly improves the workflow efficiency when dealing with complex release scenarios. + #### Configuring Conflict Resolution Policies **Update release track configuration:** @@ -459,12 +496,13 @@ GET /api/release-tracks/:id?include=all ### 6. Preview Release -Compute a release preview, which outputs a verbose diff of what will change in the next release. +Compute a release preview, which outputs a verbose diff of what will change in the next release. **This endpoint will detect and report all conflicts** that would prevent the release from proceeding, allowing editors to resolve issues before attempting to tag. + ``` GET /api/release-tracks/:id/bump/preview ``` -**Response:** +**Response (success - no conflicts):** ```json { "current_version": "1.1", @@ -507,6 +545,35 @@ GET /api/release-tracks/:id/bump/preview } ``` +**Response (with conflicts detected):** +```json +{ + "track_id": "release-track--123", + "snapshot_modified": "2024-01-15T16:20:00.000Z", + "is_already_tagged": false, + "current_version": null, + "next_version_minor": "1.2", + "next_version_major": "2.0", + "staged_count": 3, + "members_count": 2, + "candidates_count": 1, + "conflicts": [ + { + "object_ref": "attack-pattern--T1234", + "incumbent_version": "2024-01-15T10:00:00Z", + "incoming_version": "2024-02-20T10:00:00Z" + }, + { + "object_ref": "attack-pattern--T5678", + "incumbent_version": "2024-01-16T10:00:00Z", + "incoming_version": "2024-02-21T10:00:00Z" + } + ] +} +``` + +**Note:** When the `staged_to_members` conflict policy is set to `abort` and conflicts are detected, the preview will include a `conflicts` array listing **all** conflicting objects, not just the first one encountered. + ### 7. Bump with Staging ``` From 440835cb91d7bd69c1f3883dae9b4e2b1f79097b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:42:18 -0500 Subject: [PATCH 195/370] docs(release-tracks): define member sync strategy concept --- docs/COLLECTIONS_V2/00_API_REFERENCE.md | 1 + docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md | 3 + docs/COLLECTIONS_V2/06_ENTITIES.md | 9 + .../08_MEMBER_SYNC_STRATEGIES.md | 893 ++++++++++++++++++ 4 files changed, 906 insertions(+) create mode 100644 docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md diff --git a/docs/COLLECTIONS_V2/00_API_REFERENCE.md b/docs/COLLECTIONS_V2/00_API_REFERENCE.md index 6d460c11..2c1770d8 100644 --- a/docs/COLLECTIONS_V2/00_API_REFERENCE.md +++ b/docs/COLLECTIONS_V2/00_API_REFERENCE.md @@ -12,6 +12,7 @@ This document provides the complete API reference for Release Tracks V2 (formerl - [05_RELEASE_WORKFLOW.md](./05_RELEASE_WORKFLOW.md) - Workflow integration and candidacy - [06_ENTITIES.md](./06_ENTITIES.md) - Database schemas and data models - [07_OUTPUT_FORMATS.md](./07_OUTPUT_FORMATS.md) - Output format specifications +- [08_MEMBER_SYNC_STRATEGIES.md](./08_MEMBER_SYNC_STRATEGIES.md) - Automatic tracking of member object revisions **Quick Navigation:** - [Ephemeral Release Tracks](#ephemeral-release-tracks) diff --git a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md index 2550b51e..d1bbd10f 100644 --- a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md +++ b/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md @@ -8,6 +8,9 @@ This document describes how object workflow states integrate with the release tr **Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) for the complete terminology guide. +**Related Documentation:** +- [08_MEMBER_SYNC_STRATEGIES.md](./08_MEMBER_SYNC_STRATEGIES.md) - Automatic tracking of new member object revisions + ## Core Concepts ### Object Workflow States (Release Track-Centric) diff --git a/docs/COLLECTIONS_V2/06_ENTITIES.md b/docs/COLLECTIONS_V2/06_ENTITIES.md index 0ee1c312..e325697a 100644 --- a/docs/COLLECTIONS_V2/06_ENTITIES.md +++ b/docs/COLLECTIONS_V2/06_ENTITIES.md @@ -129,6 +129,15 @@ Each release track snapshot will be tracked as an individual MongoDB Document in promotion_conflicts: { candidates_to_staged: "prefer_latest", // "always_overwrite" | "always_reject" | "prefer_latest" staged_to_members: "abort" // "always_overwrite" | "always_reject" | "prefer_latest" | "abort" + }, + // Member sync strategy - controls auto-enrollment of new member object revisions + // See 08_MEMBER_SYNC_STRATEGIES.md for comprehensive documentation + member_sync: { + strategy: "track_latest", // "track_latest" | "manual" + supplant: { + behavior: "replace", // "replace" | "queue" | "ignore" + status_policy: "reset" // "reset" | "preserve" + } } }, diff --git a/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md b/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md new file mode 100644 index 00000000..70377f21 --- /dev/null +++ b/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md @@ -0,0 +1,893 @@ +# Member Sync Strategies + +## Overview + +This document describes the **Member Sync Strategy** system, which governs how release tracks respond when new revisions of member objects are created. This feature addresses a critical gap in the release track workflow: ensuring that future revisions of already-released objects are automatically queued for subsequent releases. + +**Related Documentation:** +- [00_API_REFERENCE.md](./00_API_REFERENCE.md) - Complete API reference +- [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) - Core terminology (candidates, staged, members) +- [05_RELEASE_WORKFLOW.md](./05_RELEASE_WORKFLOW.md) - Workflow states and promotion +- [06_ENTITIES.md](./06_ENTITIES.md) - Database schemas and data models + +--- + +## Problem Statement + +### The Post-Release Gap + +Consider a typical release workflow: + +1. A release track is created and objects are added as candidates +2. Objects progress through the workflow: `candidates` → `staged` → `members` +3. A release is tagged, and all staged objects are merged into `members` +4. The `staged` array is emptied + +At this point, users continue editing objects that are now in `members`. They create new revisions of techniques, groups, and other STIX objects. However, because `staged` has been emptied and there are no longer any dynamic references tracking these objects, **new revisions do not automatically appear in the release track**. + +This creates a frustrating user experience. Intuitively, users expect that once an object is enrolled in a release track's `members` list, all future revisions will automatically be queued for the next release. Instead, users must remember to manually hit the "Add Candidates" endpoint for every object they edit after each release. This is tedious, error-prone, and counterintuitive. + +### Illustrative Example + +```yaml +# Initial state: Release track after v1.0 has been tagged +release-track: + version: "1.0" + candidates: [] + staged: [] + members: + - object_ref: attack-pattern--abc + object_modified: 2025-01-01 # The v1.0 version + +# User edits attack-pattern--abc, creating a new revision +# In the database, there are now TWO versions: +objects: + - id: attack-pattern--abc + modified: 2025-01-01 # v1.0 (released) + - id: attack-pattern--abc + modified: 2025-06-15 # v1.1 (new revision) + +# PROBLEM: The release track has no idea about v1.1! +# The new revision is NOT automatically tracked. +# User must manually add it as a candidate. +``` + +### The Solution: Member Sync Strategies + +Member Sync Strategies provide configurable behavior that automatically enrolls new object revisions as candidates when the object is already a member of the release track. This eliminates the manual re-enrollment burden and aligns the system with user expectations. + +--- + +## Core Concepts + +### What is a Member Sync Strategy? + +A **Member Sync Strategy** is a configuration setting on a release track that determines how the system responds when a new revision of a member object is created. The strategy answers several questions: + +1. **Should the new revision be automatically added to candidates?** +2. **If a previous revision already exists in candidates or staged, what should happen?** +3. **What workflow status should the new revision start with?** + +### When Does Member Sync Apply? + +Member sync logic is triggered by **object modification events**. Specifically, when a STIX object is created or updated (resulting in a new `modified` timestamp), the system checks whether that object is a member of any release tracks. For each release track where the object is a member, the configured member sync strategy determines what action (if any) to take. + +**Important:** Member sync only applies to objects that are currently in the `members` array of a release track. It does not apply to objects that are only in `candidates` or `staged`. The rationale is that objects in `candidates` or `staged` are still progressing through the workflow and have not yet been "committed" to the release track as official members. + +### Relationship to Existing Features + +Member sync strategies integrate with several existing release track features: + +- **Candidacy Threshold:** When a new revision is auto-enrolled as a candidate, it may be immediately promoted to `staged` if its status meets the candidacy threshold. +- **Conflict Resolution Policies:** When member sync adds a new revision and a previous revision already exists in `candidates` or `staged`, the configured conflict resolution policy (from `config.promotion_conflicts`) determines how to handle the overlap. +- **Snapshot Creation:** Any change to a release track's object lists (`candidates`, `staged`, `members`) results in a new draft snapshot being created. Member sync follows this convention. + +--- + +## Configuration + +Member sync behavior is configured at the **release track level** via the `config.member_sync` object. This configuration applies uniformly to all member objects in the release track. + +### Configuration Schema + +```javascript +{ + config: { + // Existing configuration... + candidacy_threshold: "reviewed", + auto_promote: true, + promotion_conflicts: { + candidates_to_staged: "prefer_latest", + staged_to_members: "abort" + }, + + // Member Sync Strategy Configuration + member_sync: { + // The core sync strategy + strategy: "track_latest", // "track_latest" | "manual" + + // Supplant behavior when a new revision is created + // and an older revision already exists in candidates or staged + supplant: { + behavior: "replace", // "replace" | "queue" | "ignore" + status_policy: "reset" // "reset" | "preserve" + } + } + } +} +``` + +### Configuration Options Explained + +#### `member_sync.strategy` + +The `strategy` field determines the primary behavior of member sync. + +##### `"track_latest"` (Default for New Release Tracks) + +When a new revision of a member object is created, **automatically add it to `candidates`**. + +This is the recommended setting for most release tracks. It provides the intuitive "once enrolled, always tracked" behavior that users expect. With this strategy enabled, users can focus on editing objects without worrying about manually re-enrolling them after each release. + +**Example:** + +```yaml +# Configuration +config: + member_sync: + strategy: "track_latest" + +# Initial state after v1.0 release +members: + - object_ref: attack-pattern--abc + object_modified: 2025-01-01 + +candidates: [] +staged: [] + +# User creates a new revision of attack-pattern--abc (modified: 2025-06-15) + +# Resulting state (new draft snapshot): +members: + - object_ref: attack-pattern--abc + object_modified: 2025-01-01 # Still the released version + +candidates: + - object_ref: attack-pattern--abc + object_modified: 2025-06-15 # Automatically enrolled! + object_status: "work-in-progress" + object_added_at: "2025-06-15T10:30:00Z" + object_added_by: "system" # Indicates auto-enrollment + +staged: [] +``` + +##### `"manual"` + +Do **not** automatically enroll new revisions. Users must explicitly add new revisions via the Add Candidates endpoint (`POST /api/release-tracks/:id/candidates`). + +This setting preserves the traditional behavior and provides maximum control. It is appropriate for release tracks where only hand-picked revisions should be included, or where the team prefers explicit enrollment over automatic tracking. + +**Example:** + +```yaml +# Configuration +config: + member_sync: + strategy: "manual" + +# Initial state after v1.0 release +members: + - object_ref: attack-pattern--abc + object_modified: 2025-01-01 + +# User creates a new revision of attack-pattern--abc (modified: 2025-06-15) + +# Resulting state: NO CHANGE +# The release track is unaware of the new revision. +# User must manually add it as a candidate if they want it tracked. +``` + +#### `member_sync.supplant` + +The `supplant` configuration controls what happens when a new revision is created **and** an older revision of the same object already exists in `candidates` or `staged`. This scenario is common when users make multiple edits to an object before a release occurs. + +##### `supplant.behavior` + +Determines how to handle the coexistence of old and new revisions. + +###### `"replace"` (Default) + +Remove the older revision and add the newer revision in its place. + +This is the recommended setting for most workflows. It keeps the release track focused on the latest work and prevents accumulation of stale revisions. When combined with `status_policy: "reset"`, it ensures that significant changes trigger a re-review. + +**Example:** + +```yaml +# Configuration +config: + member_sync: + strategy: "track_latest" + supplant: + behavior: "replace" + status_policy: "reset" + +# Initial state: v26 is in staged (already reviewed) +staged: + - object_ref: attack-pattern--abc + object_modified: 2026-01-01 # v26 + object_status: "reviewed" + +# User creates v27 (modified: 2027-01-01) + +# Resulting state: +# v26 is REMOVED from staged +# v27 is ADDED to candidates with reset status + +candidates: + - object_ref: attack-pattern--abc + object_modified: 2027-01-01 # v27 + object_status: "work-in-progress" # Status reset + +staged: [] # v26 removed +``` + +###### `"queue"` + +Keep the older revision where it is and add the newer revision to `candidates` alongside it. + +This setting allows both revisions to coexist and progress through the workflow independently. It is useful when a previous revision needs to ship in an imminent release while a newer revision is still being developed for a subsequent release. + +**Example:** + +```yaml +# Configuration +config: + member_sync: + strategy: "track_latest" + supplant: + behavior: "queue" + +# Initial state: v26 is in staged (ready for next release) +staged: + - object_ref: attack-pattern--abc + object_modified: 2026-01-01 # v26 + object_status: "reviewed" + +# User creates v27 (modified: 2027-01-01) + +# Resulting state: +# v26 REMAINS in staged (will ship in next release) +# v27 is ADDED to candidates (for a future release) + +candidates: + - object_ref: attack-pattern--abc + object_modified: 2027-01-01 # v27 + object_status: "work-in-progress" + +staged: + - object_ref: attack-pattern--abc + object_modified: 2026-01-01 # v26 unchanged + object_status: "reviewed" +``` + +**Note:** When using `queue`, multiple versions of the same object can exist across `candidates` and `staged`. The existing conflict resolution policies (configured via `config.promotion_conflicts`) will handle conflicts when these versions are eventually promoted. For example, if the release track is configured with `staged_to_members: "abort"`, the system will prevent releasing if both v26 and v27 somehow end up competing for promotion to `members`. + +###### `"ignore"` + +Do not add the new revision if an older revision already exists in `candidates` or `staged`. + +This setting respects explicit version decisions. If someone has deliberately staged or queued a specific revision, the system will not override that decision with a newer revision. Users must manually remove the old revision and add the new one if they want to switch. + +**Example:** + +```yaml +# Configuration +config: + member_sync: + strategy: "track_latest" + supplant: + behavior: "ignore" + +# Initial state: v26 is in staged +staged: + - object_ref: attack-pattern--abc + object_modified: 2026-01-01 # v26 + object_status: "reviewed" + +# User creates v27 (modified: 2027-01-01) + +# Resulting state: NO CHANGE +# v27 is NOT added because v26 already exists in staged. +# The system assumes v26 was deliberately chosen and should not be overridden. +``` + +##### `supplant.status_policy` + +Determines the workflow status assigned to a new revision when `supplant.behavior` is `"replace"`. This setting is ignored when `behavior` is `"queue"` or `"ignore"`. + +###### `"reset"` (Default) + +Assign the new revision `work-in-progress` status and place it in `candidates`, regardless of where the old revision was or what status it had. + +This is the safer option. It ensures that any new revision undergoes the full review workflow, even if the previous revision had already been reviewed. The rationale is that new changes might introduce issues that require fresh review. + +**Example:** + +```yaml +# Old revision was reviewed and staged +staged: + - object_ref: attack-pattern--abc + object_modified: 2026-01-01 + object_status: "reviewed" + +# With status_policy: "reset", the new revision: +candidates: + - object_ref: attack-pattern--abc + object_modified: 2027-01-01 + object_status: "work-in-progress" # Starts fresh +``` + +###### `"preserve"` + +Assign the new revision the same status as the old revision and place it in the same tier. + +This option trusts that new revisions are at least as complete as previous ones. It accelerates the workflow by avoiding redundant reviews. This is appropriate for release tracks with trusted contributors or where changes are typically incremental refinements. + +**Example:** + +```yaml +# Old revision was reviewed and staged +staged: + - object_ref: attack-pattern--abc + object_modified: 2026-01-01 + object_status: "reviewed" + +# With status_policy: "preserve", the new revision: +staged: + - object_ref: attack-pattern--abc + object_modified: 2027-01-01 + object_status: "reviewed" # Preserved from old revision +``` + +**Caution:** Using `preserve` means that significant changes (including potentially breaking ones) could skip review. Only use this setting in release tracks where all contributors are trusted and where changes are typically low-risk. + +--- + +## Detailed Behavior Scenarios + +This section walks through various scenarios to illustrate how member sync strategies behave in practice. + +### Scenario 1: Simple Auto-Enrollment + +**Setup:** +- Release track has `track_latest` strategy +- Object `attack-pattern--T1` is in `members` (version v25) +- No pending revisions in `candidates` or `staged` + +**Event:** User creates a new revision of `attack-pattern--T1` (v26) + +**Result:** +```yaml +# Before +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +candidates: [] +staged: [] + +# After +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +candidates: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "work-in-progress" } +staged: [] +``` + +**Explanation:** The new revision v26 is automatically enrolled as a candidate. The released version v25 remains in `members`. This is the most common scenario and demonstrates the core value of member sync. + +### Scenario 2: Replacement with Status Reset + +**Setup:** +- Release track has `track_latest` strategy with `replace` + `reset` +- Object `attack-pattern--T1` has v25 in `members` +- v26 is already in `staged` with status `reviewed` + +**Event:** User creates v27 + +**Result:** +```yaml +# Before +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +candidates: [] +staged: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "reviewed" } + +# After +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +candidates: + - { object_ref: attack-pattern--T1, object_modified: v27, object_status: "work-in-progress" } +staged: [] +``` + +**Explanation:** v26 is removed from `staged` and v27 is added to `candidates` with reset status. The user will need to re-review v27 before it can be staged again. This ensures that the new changes receive proper scrutiny. + +### Scenario 3: Replacement with Status Preserved + +**Setup:** +- Release track has `track_latest` strategy with `replace` + `preserve` +- Object `attack-pattern--T1` has v25 in `members` +- v26 is in `staged` with status `reviewed` + +**Event:** User creates v27 + +**Result:** +```yaml +# Before +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +staged: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "reviewed" } + +# After +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +staged: + - { object_ref: attack-pattern--T1, object_modified: v27, object_status: "reviewed" } +``` + +**Explanation:** v26 is replaced by v27, but v27 inherits the `reviewed` status and remains in `staged`. This is faster but assumes the new changes don't require re-review. + +### Scenario 4: Queueing Alongside Existing Revision + +**Setup:** +- Release track has `track_latest` strategy with `queue` +- Object `attack-pattern--T1` has v25 in `members` +- v26 is in `staged` (ready for imminent release) + +**Event:** User creates v27 (for a future release) + +**Result:** +```yaml +# Before +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +staged: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "reviewed" } +candidates: [] + +# After +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +staged: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "reviewed" } +candidates: + - { object_ref: attack-pattern--T1, object_modified: v27, object_status: "work-in-progress" } +``` + +**Explanation:** Both v26 and v27 coexist. v26 will ship in the next release while v27 progresses through the workflow for a subsequent release. This is useful for parallel development across release cycles. + +### Scenario 5: Ignoring When Revision Already Exists + +**Setup:** +- Release track has `track_latest` strategy with `ignore` +- Object `attack-pattern--T1` has v25 in `members` +- v26 is in `candidates` (being worked on) + +**Event:** User creates v27 + +**Result:** +```yaml +# Before +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +candidates: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "work-in-progress" } + +# After: NO CHANGE +members: + - { object_ref: attack-pattern--T1, object_modified: v25 } +candidates: + - { object_ref: attack-pattern--T1, object_modified: v26, object_status: "work-in-progress" } +``` + +**Explanation:** v27 is not added because v26 already exists in `candidates`. The user must manually remove v26 and add v27 if they want to switch. This setting respects deliberate version choices. + +### Scenario 6: Multiple Objects with Mixed States + +**Setup:** +- Release track has `track_latest` with `replace` + `reset` +- Three objects in `members`: T1 (v25), T2 (v25), T3 (v25) +- T1 also has v26 in `staged` +- T2 has no pending revisions +- T3 has v26 in `candidates` + +**Event:** User creates new revisions: T1-v27, T2-v26, T3-v27 + +**Result:** +```yaml +# Before +members: + - { object_ref: T1, object_modified: v25 } + - { object_ref: T2, object_modified: v25 } + - { object_ref: T3, object_modified: v25 } +staged: + - { object_ref: T1, object_modified: v26, object_status: "reviewed" } +candidates: + - { object_ref: T3, object_modified: v26, object_status: "awaiting-review" } + +# After +members: + - { object_ref: T1, object_modified: v25 } + - { object_ref: T2, object_modified: v25 } + - { object_ref: T3, object_modified: v25 } +staged: [] # T1-v26 removed +candidates: + - { object_ref: T1, object_modified: v27, object_status: "work-in-progress" } # Replaced T1-v26 + - { object_ref: T2, object_modified: v26, object_status: "work-in-progress" } # New enrollment + - { object_ref: T3, object_modified: v27, object_status: "work-in-progress" } # Replaced T3-v26 +``` + +**Explanation:** Each object is handled according to the strategy: +- T1: v26 in `staged` is replaced by v27 in `candidates` with reset status +- T2: No existing revision, so v26 is simply enrolled in `candidates` +- T3: v26 in `candidates` is replaced by v27 in `candidates` with reset status + +### Scenario 7: Auto-Promotion After Enrollment + +**Setup:** +- Release track has: + - `track_latest` with `replace` + `reset` + - `candidacy_threshold: "work-in-progress"` (very permissive) + - `auto_promote: true` +- Object `attack-pattern--T1` has v25 in `members` + +**Event:** User creates v26 + +**Result:** +```yaml +# Before +members: + - { object_ref: T1, object_modified: v25 } +candidates: [] +staged: [] + +# After (auto-enrollment + auto-promotion) +members: + - { object_ref: T1, object_modified: v25 } +candidates: [] # Immediately promoted! +staged: + - { object_ref: T1, object_modified: v26, object_status: "work-in-progress" } +``` + +**Explanation:** v26 is auto-enrolled to `candidates`, but because the candidacy threshold is `work-in-progress` and auto-promote is enabled, v26 is immediately promoted to `staged`. This demonstrates how member sync integrates with existing promotion logic. + +--- + +## Integration with Existing Features + +### Interaction with Candidacy Threshold + +When a new revision is auto-enrolled as a candidate, the standard candidacy threshold logic applies. If the new revision's status meets or exceeds the configured threshold, and `auto_promote` is enabled, the revision will be immediately promoted to `staged`. + +This can lead to interesting scenarios: +- With `reset` status policy, the new revision starts as `work-in-progress`, which typically does not meet the default threshold of `reviewed`. +- With `preserve` status policy, a revision that replaces a `reviewed` entry in `staged` will retain `reviewed` status and could theoretically be immediately re-staged. + +### Interaction with Conflict Resolution Policies + +When `supplant.behavior` is `queue`, multiple revisions of the same object can coexist across `candidates` and `staged`. This creates potential for conflicts during promotion: + +1. **Candidates to Staged:** If v26 is in `candidates` and v27 is also in `candidates`, promoting one may conflict with the other. The `candidates_to_staged` conflict policy determines resolution. + +2. **Staged to Members:** If v26 and v27 are both in `staged` (which can happen with `queue` + subsequent manual promotions), the `staged_to_members` policy applies during release. + +The existing conflict resolution policies (`always_overwrite`, `always_reject`, `prefer_latest`, `abort`) handle these situations. No changes to conflict resolution are required for member sync to function correctly. + +### Snapshot Creation + +Any change to a release track's `candidates`, `staged`, or `members` arrays results in a new draft snapshot. Member sync follows this convention. When a new revision is auto-enrolled or an existing revision is supplanted, the system creates a new draft snapshot with the updated arrays. + +This means: +- Auto-enrollment generates a new snapshot +- Supplanting (whether `replace` or `queue`) generates a new snapshot +- Multiple objects being updated simultaneously (e.g., bulk import) generates a single snapshot reflecting all changes + +### Event-Driven Architecture + +Member sync requires listening for object modification events. When a STIX object is created or modified: + +1. The system identifies all release tracks where this object appears in `members` +2. For each relevant release track, the configured member sync strategy is evaluated +3. If the strategy dictates action (e.g., auto-enrollment), the appropriate snapshot modifications are made + +This event-driven approach ensures that member sync is reactive and automatic, requiring no manual intervention from users. + +--- + +## Default Configuration + +### Defaults for New Release Tracks + +Newly created release tracks will use the following default member sync configuration: + +```javascript +{ + member_sync: { + strategy: "track_latest", + supplant: { + behavior: "replace", + status_policy: "reset" + } + } +} +``` + +This default provides: +- **Automatic tracking** of new revisions (solves the core problem) +- **Clean replacement** of outdated revisions (prevents accumulation) +- **Safe re-review** requirement (ensures quality control) + +### Rationale for Defaults + +The defaults were chosen to balance convenience with safety: + +1. **`track_latest`** is the default because it matches user expectations. Users intuitively expect enrolled objects to be tracked continuously. + +2. **`replace`** is the default because most teams want to focus on the latest work, not accumulate stale revisions that clutter the workflow. + +3. **`reset`** is the default because it's safer. New revisions might introduce issues that weren't present in the previous revision. Requiring re-review ensures that changes receive appropriate scrutiny. + +### Migrating Existing Release Tracks + +Existing release tracks (created before this feature) will default to: + +```javascript +{ + member_sync: { + strategy: "manual", // Preserves backward-compatible behavior + supplant: { + behavior: "replace", + status_policy: "reset" + } + } +} +``` + +This ensures that existing workflows are not disrupted. Teams can opt into `track_latest` by explicitly updating their configuration. + +--- + +## API Reference + +### Updating Member Sync Configuration + +Member sync settings are managed via the existing configuration endpoint: + +``` +PUT /api/release-tracks/:id/config +``` + +**Request Body:** +```json +{ + "member_sync": { + "strategy": "track_latest", + "supplant": { + "behavior": "replace", + "status_policy": "reset" + } + } +} +``` + +**Response:** Returns the updated configuration. + +**Note:** Updating the member sync configuration does not retroactively process existing member objects. It only affects how the system responds to future object modification events. + +### Retrieving Configuration + +``` +GET /api/release-tracks/:id/config +``` + +**Response:** +```json +{ + "candidacy_threshold": "reviewed", + "auto_promote": true, + "promotion_conflicts": { + "candidates_to_staged": "prefer_latest", + "staged_to_members": "abort" + }, + "member_sync": { + "strategy": "track_latest", + "supplant": { + "behavior": "replace", + "status_policy": "reset" + } + } +} +``` + +--- + +## Decision Matrix + +The following matrix summarizes the behavior for each combination of settings: + +| Scenario | `track_latest` + `replace` + `reset` | `track_latest` + `replace` + `preserve` | `track_latest` + `queue` | `track_latest` + `ignore` | `manual` | +|----------|--------------------------------------|----------------------------------------|--------------------------|--------------------------|----------| +| New revision created (nothing in candidates/staged) | Add to candidates as WIP | Add to candidates as WIP | Add to candidates as WIP | Add to candidates as WIP | No action | +| New revision created (older in candidates as WIP) | Replace in candidates as WIP | Replace in candidates as WIP | Add alongside as WIP | No action | No action | +| New revision created (older in candidates as awaiting-review) | Replace in candidates as WIP | Replace in candidates as awaiting-review | Add alongside as WIP | No action | No action | +| New revision created (older in staged as reviewed) | Remove from staged, add to candidates as WIP | Replace in staged as reviewed | Keep in staged, add to candidates as WIP | No action | No action | + +--- + +## Best Practices + +### Recommended Configuration for Production Release Tracks + +For release tracks that publish to production environments: + +```javascript +{ + member_sync: { + strategy: "track_latest", + supplant: { + behavior: "replace", + status_policy: "reset" + } + }, + candidacy_threshold: "reviewed", + promotion_conflicts: { + candidates_to_staged: "prefer_latest", + staged_to_members: "abort" + } +} +``` + +**Rationale:** +- `track_latest` ensures no revisions are missed +- `replace` + `reset` ensures all changes are reviewed +- `staged_to_members: "abort"` prevents accidental overwrites during release + +### Recommended Configuration for Development Release Tracks + +For release tracks used in development or testing: + +```javascript +{ + member_sync: { + strategy: "track_latest", + supplant: { + behavior: "replace", + status_policy: "preserve" + } + }, + candidacy_threshold: "work-in-progress", + auto_promote: true +} +``` + +**Rationale:** +- `track_latest` ensures continuous tracking +- `preserve` speeds up iteration by not requiring re-review +- Permissive threshold allows rapid release cycles + +### When to Use `queue` Behavior + +Use `queue` when: +- Your team works on multiple release cycles in parallel +- You need to ship hotfixes while continuing development on the next major version +- You want to preserve staged work while tracking new development + +### When to Use `ignore` Behavior + +Use `ignore` when: +- Specific version pinning is important for compliance or reproducibility +- You want manual control over which revisions enter the workflow +- Your team makes deliberate decisions about version selection + +### When to Use `manual` Strategy + +Use `manual` when: +- You only want hand-picked revisions in the release track +- The release track is for archival purposes (capturing specific historical state) +- You're migrating from an older workflow and want to preserve existing behavior + +--- + +## Limitations and Future Considerations + +### Release-Track-Level Configuration Only + +Member sync configuration applies uniformly to all member objects in a release track. There is no per-object override mechanism in the current design. + +**Rationale:** Per-object overrides would require tracking configuration on individual object references across three tiers (`candidates`, `staged`, `members`). This significantly increases schema complexity and API surface area. The current release track schema and API are not designed for per-object configuration, and introducing it would require substantial refactoring. + +**Future Consideration:** If use cases emerge where per-object sync behavior is essential (e.g., specific objects that should never be auto-tracked), this limitation can be revisited. A potential approach would be to introduce an "exclusion list" that specifies objects exempt from member sync, rather than full per-object configuration. + +### No Retroactive Processing + +Changing the member sync configuration does not retroactively process existing member objects. For example, if you switch from `manual` to `track_latest`, the system will not immediately scan all members and enroll their latest revisions. It will only respond to future object modifications. + +**Workaround:** If you need to bulk-enroll the latest revisions of all member objects after switching to `track_latest`, you can use the Add Candidates endpoint with the list of member object IDs (without specifying `modified`, which defaults to latest). + +### Event Ordering + +When multiple objects are modified in rapid succession (e.g., during a bulk import), the system processes events in order. This should not cause issues in normal operation, but be aware that: +- Each modification event may trigger a new snapshot +- The final state reflects all modifications, but intermediate snapshots may exist + +--- + +## Glossary + +| Term | Definition | +|------|------------| +| **Member Sync** | The system that automatically responds to new object revisions by enrolling them in release tracks | +| **Auto-enrollment** | The act of automatically adding a new object revision to `candidates` | +| **Supplant** | The act of replacing an older revision with a newer one | +| **Status Policy** | The rule determining what workflow status a new revision receives during supplanting | +| **Track Latest** | A sync strategy that automatically enrolls new revisions of member objects | +| **Manual** | A sync strategy that requires explicit user action to enroll new revisions | + +--- + +## Appendix: Schema Update + +The release track configuration schema is extended as follows: + +```javascript +// In release-track-snapshot-schema.js + +config: { + // Existing fields... + candidacy_threshold: { + type: String, + enum: ["work-in-progress", "awaiting-review", "reviewed"], + default: "reviewed" + }, + auto_promote: { + type: Boolean, + default: true + }, + promotion_conflicts: { + candidates_to_staged: { + type: String, + enum: ["always_overwrite", "always_reject", "prefer_latest"], + default: "prefer_latest" + }, + staged_to_members: { + type: String, + enum: ["always_overwrite", "always_reject", "prefer_latest", "abort"], + default: "abort" + } + }, + + // NEW: Member Sync Configuration + member_sync: { + strategy: { + type: String, + enum: ["track_latest", "manual"], + default: "track_latest" // For new tracks + }, + supplant: { + behavior: { + type: String, + enum: ["replace", "queue", "ignore"], + default: "replace" + }, + status_policy: { + type: String, + enum: ["reset", "preserve"], + default: "reset" + } + } + } +} +``` From d1f95e019ba76ff701df05fcc6e69cba4aec8282 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:49:00 -0500 Subject: [PATCH 196/370] feat(release-tracks): implement export and ephemeral service - new file (ephemeral-service): generates stateless, non-persisted stix bundles for all att&ck domains - new file (export-service): hydrates stix object refs into full stix docs, then formats into any of 3 output formats - new file (bundle-import-service): parses a stix bundle and creates a new release track from it - mod file (release-tracks-service): implement createTrackFromBundle, getEphemeralBundle, and add dynamic res body formatting to getLatestSnapshot and getSnapshotByModified This concludes phase 6 of the release track service layer implementation plan --- .../release-tracks/bundle-import-service.js | 313 ++++++++++++++++++ .../release-tracks/ephemeral-service.js | 281 ++++++++++++++++ app/services/release-tracks/export-service.js | 266 +++++++++++++++ .../release-tracks/release-tracks-service.js | 38 ++- 4 files changed, 886 insertions(+), 12 deletions(-) create mode 100644 app/services/release-tracks/bundle-import-service.js create mode 100644 app/services/release-tracks/ephemeral-service.js create mode 100644 app/services/release-tracks/export-service.js diff --git a/app/services/release-tracks/bundle-import-service.js b/app/services/release-tracks/bundle-import-service.js new file mode 100644 index 00000000..412fcf51 --- /dev/null +++ b/app/services/release-tracks/bundle-import-service.js @@ -0,0 +1,313 @@ +'use strict'; + +// ============================================================================= +// Bundle Import Service +// +// Parses a STIX 2.1 bundle and creates a new release track from it. +// +// This is an independent implementation that does NOT depend on the legacy +// collection-bundles infrastructure. It will eventually supplant that system +// once it has been tested, validated, and shipped. +// +// The import process: +// 1. Extract collection metadata (if an x-mitre-collection object is present) +// 2. Sort non-collection objects by dependency order +// 3. Import each object into the database (skip duplicates) +// 4. Build member entries from all imported/existing objects +// 5. Create a new release track with those members +// ============================================================================= + +const types = require('../../lib/types'); +const logger = require('../../lib/logger'); +const snapshotService = require('./snapshot-service'); +const { BadRequestError, DuplicateIdError } = require('../../exceptions'); + +// --------------------------------------------------------------------------- +// Service map — lazy-loaded to avoid circular dependency issues at startup. +// +// Maps STIX type prefixes to STIX services so we can call +// `service.create(data, { import: true })` for each object. +// --------------------------------------------------------------------------- + +let _serviceMap = null; + +function getServiceMap() { + if (_serviceMap) return _serviceMap; + + _serviceMap = { + [types.Technique]: require('../stix/techniques-service'), + [types.Tactic]: require('../stix/tactics-service'), + [types.Group]: require('../stix/groups-service'), + [types.Campaign]: require('../stix/campaigns-service'), + [types.Mitigation]: require('../stix/mitigations-service'), + [types.Matrix]: require('../stix/matrices-service'), + [types.Relationship]: require('../stix/relationships-service'), + [types.MarkingDefinition]: require('../stix/marking-definitions-service'), + [types.Identity]: require('../stix/identities-service'), + [types.Note]: require('../../services/system/notes-service'), + [types.DataSource]: require('../stix/data-sources-service'), + [types.DataComponent]: require('../stix/data-components-service'), + [types.Asset]: require('../stix/assets-service'), + [types.Analytic]: require('../stix/analytics-service'), + [types.DetectionStrategy]: require('../stix/detection-strategies-service'), + }; + + // Software types share a single service + const softwareService = require('../stix/software-service'); + _serviceMap[types.Malware] = softwareService; + _serviceMap[types.Tool] = softwareService; + + return _serviceMap; +} + +// --------------------------------------------------------------------------- +// Dependency sort order — ensures referenced objects are created before +// objects that reference them. Same ordering as import-bundle.js. +// --------------------------------------------------------------------------- + +const TYPE_SORT_ORDER = { + [types.MarkingDefinition]: 0, + [types.Identity]: 1, + [types.DataSource]: 2, + [types.DataComponent]: 3, + [types.Analytic]: 4, + [types.DetectionStrategy]: 5, + [types.Technique]: 6, + [types.Tactic]: 7, + [types.Mitigation]: 8, + [types.Group]: 9, + [types.Campaign]: 10, + [types.Malware]: 11, + [types.Tool]: 12, + [types.Asset]: 13, + [types.Matrix]: 14, + [types.Relationship]: 15, + [types.Note]: 16, +}; + +function getTypeSortOrder(stixType) { + return TYPE_SORT_ORDER[stixType] ?? 99; +} + +// ============================================================================= +// Internal helpers +// ============================================================================= + +/** + * Extract the x-mitre-collection object from the bundle (if present). + * Returns `{ collectionObj, otherObjects }`. + */ +function extractCollectionObject(objects) { + let collectionObj = null; + const otherObjects = []; + + for (const obj of objects) { + if (obj.type === 'x-mitre-collection') { + // Take the first collection object; ignore duplicates + if (!collectionObj) { + collectionObj = obj; + } else { + logger.warn('BundleImportService: Multiple x-mitre-collection objects found; using first'); + } + } else { + otherObjects.push(obj); + } + } + + return { collectionObj, otherObjects }; +} + +/** + * Sort objects by dependency order for safe sequential import. + */ +function sortByDependencyOrder(objects) { + return [...objects].sort((a, b) => getTypeSortOrder(a.type) - getTypeSortOrder(b.type)); +} + +/** + * Import a single STIX object into the database, skipping if it already exists. + * + * @param {Object} stixObj - A raw STIX object from the bundle + * @param {Object} serviceMap - Type → service mapping + * @returns {Promise<{imported: boolean, ref: {object_ref: string, object_modified: string}}>} + */ +async function importObject(stixObj, serviceMap) { + const service = serviceMap[stixObj.type]; + if (!service) { + logger.warn( + `BundleImportService: No service for type "${stixObj.type}", skipping "${stixObj.id}"`, + ); + return { imported: false, ref: null }; + } + + // Validate required fields + if (!stixObj.id || !stixObj.modified) { + logger.warn( + `BundleImportService: Object missing id or modified, skipping: ${JSON.stringify({ id: stixObj.id, type: stixObj.type })}`, + ); + return { imported: false, ref: null }; + } + + const ref = { + object_ref: stixObj.id, + object_modified: stixObj.modified, + }; + + // Check if this exact version already exists + try { + const existing = await service.retrieveVersionById(stixObj.id, stixObj.modified); + if (existing) { + logger.verbose( + `BundleImportService: Object "${stixObj.id}" @ ${stixObj.modified} already exists, skipping`, + ); + return { imported: false, ref }; + } + } catch (err) { + // retrieveVersionById may throw for various reasons; proceed with create attempt + logger.debug( + `BundleImportService: Could not check existence of "${stixObj.id}": ${err.message}`, + ); + } + + // Create the object + try { + const data = { + stix: stixObj, + workspace: {}, + }; + + await service.create(data, { import: true }); + + logger.verbose(`BundleImportService: Imported "${stixObj.type}" "${stixObj.id}"`); + return { imported: true, ref }; + } catch (err) { + if (err instanceof DuplicateIdError || err.name === 'DuplicateIdError') { + // Race condition or index-level duplicate — treat as already-existing + logger.verbose( + `BundleImportService: Duplicate detected for "${stixObj.id}", treating as existing`, + ); + return { imported: false, ref }; + } + + // Non-duplicate errors are logged but don't abort the entire import + logger.error(`BundleImportService: Failed to import "${stixObj.id}":`, err); + return { imported: false, ref }; + } +} + +// ============================================================================= +// Public API +// ============================================================================= + +/** + * Create a new release track from a STIX bundle. + * + * The bundle is parsed, its objects are imported into the database (skipping + * duplicates), and a new standard release track is created with the imported + * objects as members. + * + * If the bundle contains an `x-mitre-collection` object, its `name` and + * `description` are used as track metadata, and its `x_mitre_contents` (if + * present) is used as the authoritative member list. + * + * @param {Object} bundleData - Validated bundle: { type: 'bundle', id, objects } + * @returns {Promise} The created track's initial snapshot + */ +exports.createTrackFromBundle = async function createTrackFromBundle(bundleData) { + if (!bundleData || !Array.isArray(bundleData.objects) || bundleData.objects.length === 0) { + throw new BadRequestError({ + message: 'Invalid bundle: must contain at least one object', + }); + } + + logger.verbose( + `BundleImportService: Processing bundle "${bundleData.id}" with ${bundleData.objects.length} object(s)`, + ); + + // ------------------------------------------------------------------ + // Step 1: Extract collection metadata + // ------------------------------------------------------------------ + + const { collectionObj, otherObjects } = extractCollectionObject(bundleData.objects); + + const trackName = collectionObj?.name || 'Imported Track'; + const trackDescription = collectionObj?.description || `Imported from bundle ${bundleData.id}`; + + // ------------------------------------------------------------------ + // Step 2: Sort and import objects + // ------------------------------------------------------------------ + + const sorted = sortByDependencyOrder(otherObjects); + const serviceMap = getServiceMap(); + + const importedRefs = []; + let importedCount = 0; + let skippedCount = 0; + + for (const stixObj of sorted) { + const { imported, ref } = await importObject(stixObj, serviceMap); + if (ref) { + importedRefs.push(ref); + } + if (imported) importedCount++; + else if (ref) skippedCount++; + } + + // ------------------------------------------------------------------ + // Step 3: Determine member entries + // ------------------------------------------------------------------ + + let memberEntries; + + if ( + collectionObj && + Array.isArray(collectionObj.x_mitre_contents) && + collectionObj.x_mitre_contents.length > 0 + ) { + // Use the collection's x_mitre_contents as the authoritative member list + memberEntries = collectionObj.x_mitre_contents.map((entry) => ({ + object_ref: entry.object_ref, + object_modified: entry.object_modified, + })); + logger.verbose( + `BundleImportService: Using collection x_mitre_contents (${memberEntries.length} entries)`, + ); + } else { + // Fall back to using all imported object refs + memberEntries = importedRefs; + logger.verbose( + `BundleImportService: Using imported objects as members (${memberEntries.length} entries)`, + ); + } + + // ------------------------------------------------------------------ + // Step 4: Create the release track + // ------------------------------------------------------------------ + + const snapshot = await snapshotService.createTrack({ + name: trackName, + description: trackDescription, + type: 'standard', + }); + + // Add members by cloning the initial (empty) snapshot with the member entries + if (memberEntries.length > 0) { + const finalSnapshot = await snapshotService.cloneSnapshot(snapshot.id, snapshot, { + members: memberEntries, + }); + + logger.verbose( + `BundleImportService: Created track "${trackName}" (${snapshot.id}) ` + + `with ${memberEntries.length} member(s) ` + + `(${importedCount} imported, ${skippedCount} already existed)`, + ); + + return finalSnapshot; + } + + logger.verbose( + `BundleImportService: Created track "${trackName}" (${snapshot.id}) with 0 members`, + ); + + return snapshot; +}; diff --git a/app/services/release-tracks/ephemeral-service.js b/app/services/release-tracks/ephemeral-service.js new file mode 100644 index 00000000..f551cdfb --- /dev/null +++ b/app/services/release-tracks/ephemeral-service.js @@ -0,0 +1,281 @@ +'use strict'; + +// ============================================================================= +// Ephemeral Service +// +// Generates stateless, non-persisted STIX bundles for a given ATT&CK domain. +// Unlike regular release tracks (which store snapshots with object refs), +// ephemeral bundles are computed on-the-fly by querying all STIX repositories +// for objects belonging to the requested domain. +// +// This service performs cross-service READS (permitted by the event-driven +// architecture — see docs/CROSS_SERVICE_READS_PATTERN.md) by querying STIX +// repositories directly. It does NOT write to any repository. +// +// The domain query pattern mirrors stix-bundles-service.exportBundle, but +// operates independently of the legacy collection-bundles infrastructure. +// ============================================================================= + +const uuid = require('uuid'); +const logger = require('../../lib/logger'); + +// --------------------------------------------------------------------------- +// Domain mapping +// --------------------------------------------------------------------------- + +const DOMAIN_MAP = { + enterprise: 'enterprise-attack', + ics: 'ics-attack', + mobile: 'mobile-attack', +}; + +// --------------------------------------------------------------------------- +// Repository references — lazy-loaded to avoid circular dependencies. +// +// Only repos whose models have `stix.x_mitre_domains` are queried directly. +// Relationships, identities, and marking definitions are discovered through +// references in primary objects. +// --------------------------------------------------------------------------- + +let _repos = null; + +function getRepositories() { + if (_repos) return _repos; + + _repos = { + technique: require('../../repository/techniques-repository'), + tactic: require('../../repository/tactics-repository'), + mitigation: require('../../repository/mitigations-repository'), + software: require('../../repository/software-repository'), + matrix: require('../../repository/matrix-repository'), + analytic: require('../../repository/analytics-repository'), + dataComponent: require('../../repository/data-components-repository'), + dataSource: require('../../repository/data-sources-repository'), + asset: require('../../repository/assets-repository'), + relationship: require('../../repository/relationships-repository'), + identity: require('../../repository/identities-repository'), + markingDefinition: require('../../repository/marking-definitions-repository'), + }; + + return _repos; +} + +// ============================================================================= +// Internal helpers +// ============================================================================= + +/** + * Collect unique identity and marking-definition STIX IDs referenced by a + * set of STIX documents so we can fetch them in a single batch. + */ +function collectReferencedIds(documents) { + const identityIds = new Set(); + const markingIds = new Set(); + + for (const doc of documents) { + const stix = doc.stix; + if (stix.created_by_ref) identityIds.add(stix.created_by_ref); + if (Array.isArray(stix.object_marking_refs)) { + for (const ref of stix.object_marking_refs) markingIds.add(ref); + } + } + + return { identityIds, markingIds }; +} + +/** + * Fetch identities and marking definitions by their STIX IDs. + */ +async function fetchSupportingObjects(identityIds, markingIds) { + const repos = getRepositories(); + const results = []; + + // Fetch identities + await Promise.all( + [...identityIds].map(async (id) => { + try { + const doc = await repos.identity.retrieveLatestByStixId(id); + if (doc) results.push(doc); + } catch (err) { + logger.warn(`EphemeralService: Could not fetch identity "${id}": ${err.message}`); + } + }), + ); + + // Fetch marking definitions + await Promise.all( + [...markingIds].map(async (id) => { + try { + const doc = await repos.markingDefinition.retrieveLatestByStixId(id); + if (doc) results.push(doc); + } catch (err) { + logger.warn(`EphemeralService: Could not fetch marking definition "${id}": ${err.message}`); + } + }), + ); + + return results; +} + +// ============================================================================= +// Public API +// ============================================================================= + +/** + * Generate an ephemeral STIX bundle for a domain. + * + * Queries all domain-aware repositories in parallel for the latest version + * of each object in the given domain, then discovers and includes + * relationships that connect those objects, along with referenced identities + * and marking definitions. + * + * @param {string} domain - One of: 'enterprise', 'ics', 'mobile' + * @param {string} [format='bundle'] - Output format (currently only 'bundle') + * @returns {Promise} A STIX bundle (or formatted output) + */ +exports.getEphemeralBundle = async function getEphemeralBundle(domain, format) { + const attackDomain = DOMAIN_MAP[domain]; + if (!attackDomain) { + const { BadRequestError } = require('../../exceptions'); + throw new BadRequestError({ + message: `Unknown domain: "${domain}"`, + details: `Valid domains are: ${Object.keys(DOMAIN_MAP).join(', ')}`, + }); + } + + const repos = getRepositories(); + const queryOptions = { + includeRevoked: false, + includeDeprecated: false, + }; + + logger.verbose(`EphemeralService: Generating ephemeral bundle for domain "${attackDomain}"`); + + // ------------------------------------------------------------------ + // Step 1: Query all domain-aware repositories in parallel + // ------------------------------------------------------------------ + + const [ + techniques, + tactics, + mitigations, + software, + matrices, + analytics, + dataComponents, + dataSources, + assets, + ] = await Promise.all([ + repos.technique.retrieveAllByDomain(attackDomain, queryOptions), + repos.tactic.retrieveAllByDomain(attackDomain, queryOptions), + repos.mitigation.retrieveAllByDomain(attackDomain, queryOptions), + repos.software.retrieveAllByDomain(attackDomain, queryOptions), + repos.matrix.retrieveAllByDomain(attackDomain, queryOptions), + repos.analytic.retrieveAllByDomain(attackDomain, queryOptions), + repos.dataComponent.retrieveAllByDomain(attackDomain, queryOptions), + repos.dataSource.retrieveAllByDomain(attackDomain, queryOptions), + repos.asset.retrieveAllByDomain(attackDomain, queryOptions), + ]); + + const primaryObjects = [ + ...techniques, + ...tactics, + ...mitigations, + ...software, + ...matrices, + ...analytics, + ...dataComponents, + ...dataSources, + ...assets, + ]; + + // ------------------------------------------------------------------ + // Step 2: Build a lookup of primary object IDs for relationship filtering + // ------------------------------------------------------------------ + + const primaryIdSet = new Set(primaryObjects.map((doc) => doc.stix.id)); + + // ------------------------------------------------------------------ + // Step 3: Fetch relationships that connect primary objects + // ------------------------------------------------------------------ + + const allRelationships = await repos.relationship.retrieveAll({ + versions: 'latest', + includeRevoked: false, + includeDeprecated: false, + }); + + const relevantRelationships = (allRelationships.data || allRelationships).filter( + (rel) => primaryIdSet.has(rel.stix.source_ref) || primaryIdSet.has(rel.stix.target_ref), + ); + + // ------------------------------------------------------------------ + // Step 4: Fetch supporting objects (identities, marking definitions) + // ------------------------------------------------------------------ + + const allDocs = [...primaryObjects, ...relevantRelationships]; + const { identityIds, markingIds } = collectReferencedIds(allDocs); + const supportingObjects = await fetchSupportingObjects(identityIds, markingIds); + + // ------------------------------------------------------------------ + // Step 5: Assemble the STIX bundle + // ------------------------------------------------------------------ + + // Deduplicate by stix.id (in case of overlapping supporting objects) + const seen = new Set(); + const bundleObjects = []; + + for (const doc of [...primaryObjects, ...relevantRelationships, ...supportingObjects]) { + const key = `${doc.stix.id}::${doc.stix.modified}`; + if (seen.has(key)) continue; + seen.add(key); + bundleObjects.push(doc.stix); + } + + const bundle = { + type: 'bundle', + id: `bundle--${uuid.v4()}`, + objects: bundleObjects, + }; + + logger.verbose( + `EphemeralService: Built ephemeral bundle for "${attackDomain}" ` + + `(${primaryObjects.length} primary, ${relevantRelationships.length} relationships, ` + + `${supportingObjects.length} supporting → ${bundleObjects.length} total objects)`, + ); + + // Format conversion (if not plain bundle) + if (format === 'workbench' || format === 'filesystemstore') { + // Re-use export-service formatters with a synthetic snapshot envelope + const exportService = require('./export-service'); + const syntheticDocs = [...primaryObjects, ...relevantRelationships, ...supportingObjects]; + const deduped = []; + const dedupSeen = new Set(); + for (const doc of syntheticDocs) { + const key = `${doc.stix.id}::${doc.stix.modified}`; + if (dedupSeen.has(key)) continue; + dedupSeen.add(key); + deduped.push(doc); + } + + const syntheticSnapshot = { + id: `ephemeral-${domain}`, + version: null, + name: `${domain} (ephemeral)`, + modified: new Date(), + members: deduped.map((doc) => ({ + object_ref: doc.stix.id, + object_modified: doc.stix.modified, + })), + }; + + if (format === 'workbench') { + return exportService.formatAsWorkbench(syntheticSnapshot, deduped); + } + if (format === 'filesystemstore') { + return exportService.formatAsFilesystemStore(syntheticSnapshot, deduped); + } + } + + return bundle; +}; diff --git a/app/services/release-tracks/export-service.js b/app/services/release-tracks/export-service.js new file mode 100644 index 00000000..f96bc681 --- /dev/null +++ b/app/services/release-tracks/export-service.js @@ -0,0 +1,266 @@ +'use strict'; + +// ============================================================================= +// Export Service +// +// Hydrates STIX object refs (from snapshot members/staged/candidates tiers) +// into full STIX documents, then formats the output as one of: +// - bundle: Standard STIX 2.1 bundle +// - workbench: Custom format with workflow metadata +// - filesystemstore: Directory structure organized by STIX type +// +// This service performs cross-service READS (permitted by the event-driven +// architecture — see docs/CROSS_SERVICE_READS_PATTERN.md) by querying STIX +// repositories directly. It does NOT write to any external repository. +// ============================================================================= + +const uuid = require('uuid'); +const types = require('../../lib/types'); +const logger = require('../../lib/logger'); + +// --------------------------------------------------------------------------- +// Repository map — lazy-loaded to avoid circular dependency issues at startup. +// +// Maps STIX type prefixes to their corresponding repositories so we can +// batch-query each repository's `findManyByIdAndModified` in parallel. +// --------------------------------------------------------------------------- + +let _repoMap = null; + +function getRepositoryMap() { + if (_repoMap) return _repoMap; + + _repoMap = { + [types.Technique]: require('../../repository/techniques-repository'), + [types.Tactic]: require('../../repository/tactics-repository'), + [types.Group]: require('../../repository/groups-repository'), + [types.Campaign]: require('../../repository/campaigns-repository'), + [types.Mitigation]: require('../../repository/mitigations-repository'), + [types.Matrix]: require('../../repository/matrix-repository'), + [types.Relationship]: require('../../repository/relationships-repository'), + [types.MarkingDefinition]: require('../../repository/marking-definitions-repository'), + [types.Identity]: require('../../repository/identities-repository'), + [types.Note]: require('../../repository/notes-repository'), + [types.DataSource]: require('../../repository/data-sources-repository'), + [types.DataComponent]: require('../../repository/data-components-repository'), + [types.Asset]: require('../../repository/assets-repository'), + [types.Analytic]: require('../../repository/analytics-repository'), + [types.DetectionStrategy]: require('../../repository/detection-strategies-repository'), + }; + + // Software types share a single repository + const softwareRepo = require('../../repository/software-repository'); + _repoMap[types.Malware] = softwareRepo; + _repoMap[types.Tool] = softwareRepo; + + return _repoMap; +} + +// ============================================================================= +// Hydration +// ============================================================================= + +/** + * Hydrate an array of tier entries into full STIX documents. + * + * Groups entries by STIX type (extracted from the `object_ref` prefix) and + * batch-queries each repository in parallel via `findManyByIdAndModified`. + * + * @param {Array<{object_ref: string, object_modified: string|Date}>} entries + * @returns {Promise>} Full Mongoose lean documents ({ stix, workspace, ... }) + */ +exports.hydrateMembers = async function hydrateMembers(entries) { + if (!entries || entries.length === 0) return []; + + // Group entries by STIX type prefix + const byType = {}; + for (const entry of entries) { + const type = entry.object_ref.split('--')[0]; + if (!byType[type]) byType[type] = []; + byType[type].push(entry); + } + + const repoMap = getRepositoryMap(); + const hydrated = []; + + await Promise.all( + Object.entries(byType).map(async ([type, refs]) => { + const repo = repoMap[type]; + if (!repo) { + logger.warn( + `ExportService: No repository for type "${type}", skipping ${refs.length} object(s)`, + ); + return; + } + try { + const docs = await repo.findManyByIdAndModified(refs); + hydrated.push(...docs); + } catch (err) { + logger.error(`ExportService: Failed to hydrate ${refs.length} "${type}" object(s):`, err); + } + }), + ); + + return hydrated; +}; + +// ============================================================================= +// Format helpers +// ============================================================================= + +/** + * Build a lookup from (object_ref::object_modified_ms) → tier name. + * Used by the workbench formatter to annotate each object with its tier. + */ +function buildTierLookup(snapshot) { + const lookup = {}; + for (const m of snapshot.members || []) { + lookup[`${m.object_ref}::${new Date(m.object_modified).getTime()}`] = 'released'; + } + for (const s of snapshot.staged || []) { + lookup[`${s.object_ref}::${new Date(s.object_modified).getTime()}`] = 'staged'; + } + for (const c of snapshot.candidates || []) { + lookup[`${c.object_ref}::${new Date(c.object_modified).getTime()}`] = 'candidate'; + } + return lookup; +} + +/** + * Format as a standard STIX 2.1 bundle. + * + * Only includes `stix` properties — no workspace data or workflow metadata. + */ +exports.formatAsBundle = function formatAsBundle(snapshot, hydratedObjects) { + return { + type: 'bundle', + id: `bundle--${uuid.v4()}`, + objects: hydratedObjects.map((doc) => doc.stix), + }; +}; + +/** + * Format as a workbench-optimized response with full metadata. + * + * Includes `stix` + `workspace` properties and tier annotations. + * The `include` option controls whether staged/candidates are included. + */ +exports.formatAsWorkbench = function formatAsWorkbench(snapshot, hydratedObjects) { + const tierLookup = buildTierLookup(snapshot); + + const objects = hydratedObjects.map((doc) => { + const key = `${doc.stix.id}::${new Date(doc.stix.modified).getTime()}`; + return { + stix: doc.stix, + workspace: doc.workspace || {}, + metadata: { + collection_tier: tierLookup[key] || 'released', + object_type: doc.stix.type, + object_name: doc.stix.name || doc.stix.id, + }, + }; + }); + + return { + collection: { + id: snapshot.id, + version: snapshot.version, + name: snapshot.name, + modified: snapshot.modified, + }, + objects, + summary: { + released_count: (snapshot.members || []).length, + staged_count: (snapshot.staged || []).length, + candidate_count: (snapshot.candidates || []).length, + }, + }; +}; + +/** + * Format as a FileSystemStore-compatible directory structure. + * + * Objects are grouped by STIX type, each with a filename and content property. + * See docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md for specification. + */ +exports.formatAsFilesystemStore = function formatAsFilesystemStore(snapshot, hydratedObjects) { + const structure = {}; + + for (const doc of hydratedObjects) { + const type = doc.stix.type; + if (!structure[type]) structure[type] = []; + structure[type].push({ + filename: `${doc.stix.id}.json`, + content: doc.stix, + }); + } + + return { + format: 'filesystemstore', + track_id: snapshot.id, + version: snapshot.version, + structure, + }; +}; + +// ============================================================================= +// Main export entry point +// ============================================================================= + +/** + * Export a snapshot in the specified format. + * + * This is the primary entry point called by the facade when a `format` + * query parameter is provided on snapshot retrieval endpoints. + * + * @param {Object} snapshot - The raw snapshot document from the dynamic repo + * @param {string} format - One of: 'bundle', 'workbench', 'filesystemstore' + * @param {Object} [options] - Additional options + * @param {string} [options.include] - For workbench format: 'staged', 'candidates', or 'all' + * @returns {Promise} The formatted export + */ +exports.exportSnapshot = async function exportSnapshot(snapshot, format, options = {}) { + const members = snapshot.members || []; + + if (format === 'bundle') { + if (members.length === 0) { + return { type: 'bundle', id: `bundle--${uuid.v4()}`, objects: [] }; + } + const hydratedMembers = await exports.hydrateMembers(members); + return exports.formatAsBundle(snapshot, hydratedMembers); + } + + if (format === 'workbench') { + // Workbench format optionally includes staged and/or candidate objects + const allRefs = [...members]; + if (options.include === 'staged' || options.include === 'all') { + allRefs.push(...(snapshot.staged || [])); + } + if (options.include === 'candidates' || options.include === 'all') { + allRefs.push(...(snapshot.candidates || [])); + } + + if (allRefs.length === 0) { + return exports.formatAsWorkbench(snapshot, []); + } + const hydratedAll = await exports.hydrateMembers(allRefs); + return exports.formatAsWorkbench(snapshot, hydratedAll); + } + + if (format === 'filesystemstore') { + if (members.length === 0) { + return { + format: 'filesystemstore', + track_id: snapshot.id, + version: snapshot.version, + structure: {}, + }; + } + const hydratedMembers = await exports.hydrateMembers(members); + return exports.formatAsFilesystemStore(snapshot, hydratedMembers); + } + + // Unknown format — return raw snapshot unchanged + logger.warn(`ExportService: Unknown format "${format}", returning raw snapshot`); + return snapshot; +}; diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index c20304d1..5fc1c5c2 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ -// TODO remove the above eslint rule after all sub-services have been implemented 'use strict'; // ============================================================================= @@ -13,7 +11,7 @@ // Phase 3: Auto-promotion, workflow → workflow-service // Phase 4: Bump/tag, versioning → versioning-service // Phase 5: Virtual track composition → virtual-track-service -// Phase 6: Export, ephemeral → export-service, ephemeral-service (TODO) +// Phase 6: Export, ephemeral, bundle import → export-service, ephemeral-service, bundle-import-service // ============================================================================= const { NotImplementedError } = require('../../exceptions'); @@ -21,6 +19,9 @@ const snapshotService = require('./snapshot-service'); const standardTrackService = require('./standard-track-service'); const versioningService = require('./versioning-service'); const virtualTrackService = require('./virtual-track-service'); +const exportService = require('./export-service'); +const ephemeralService = require('./ephemeral-service'); +const bundleImportService = require('./bundle-import-service'); const MODULE = 'release-tracks-service'; @@ -40,20 +41,33 @@ exports.createTrack = function createTrack(data) { return snapshotService.createTrack(data); }; -exports.createTrackFromBundle = async function createTrackFromBundle(_bundleData) { - notImplemented('createTrackFromBundle'); +// Phase 6 → bundle-import-service +exports.createTrackFromBundle = function createTrackFromBundle(bundleData) { + return bundleImportService.createTrackFromBundle(bundleData); }; +// eslint-disable-next-line no-unused-vars exports.importTrack = async function importTrack(_data) { notImplemented('importTrack'); }; -exports.getLatestSnapshot = function getLatestSnapshot(trackId, options) { - return snapshotService.getLatestSnapshot(trackId, options); +// Phase 6: Format-aware snapshot retrieval +// If options.format is specified, the raw snapshot is hydrated and formatted +// via export-service. Otherwise the raw snapshot is returned as before. +exports.getLatestSnapshot = async function getLatestSnapshot(trackId, options) { + const snapshot = await snapshotService.getLatestSnapshot(trackId, options); + if (options && options.format) { + return exportService.exportSnapshot(snapshot, options.format, options); + } + return snapshot; }; -exports.getSnapshotByModified = function getSnapshotByModified(trackId, modified, options) { - return snapshotService.getSnapshotByModified(trackId, modified, options); +exports.getSnapshotByModified = async function getSnapshotByModified(trackId, modified, options) { + const snapshot = await snapshotService.getSnapshotByModified(trackId, modified, options); + if (options && options.format) { + return exportService.exportSnapshot(snapshot, options.format, options); + } + return snapshot; }; exports.updateMetadata = function updateMetadata(trackId, updates, userId) { @@ -99,11 +113,11 @@ exports.deleteSnapshot = function deleteSnapshot(trackId, modified) { }; // ----------------------------------------------------------------------------- -// Ephemeral (Phase 6 → ephemeral-service, TODO) +// Ephemeral (Phase 6 → ephemeral-service) // ----------------------------------------------------------------------------- -exports.getEphemeralBundle = async function getEphemeralBundle(_domain, _format) { - notImplemented('getEphemeralBundle'); +exports.getEphemeralBundle = function getEphemeralBundle(domain, format) { + return ephemeralService.getEphemeralBundle(domain, format); }; // ----------------------------------------------------------------------------- From 5a0a470e570da4c26e3e24c8b64fed038b51de0a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:14:08 -0500 Subject: [PATCH 197/370] docs(release-tracks): remove section on migration handling for existing tracks --- .../08_MEMBER_SYNC_STRATEGIES.md | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md b/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md index 70377f21..1d3c7c81 100644 --- a/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md +++ b/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md @@ -609,9 +609,7 @@ This event-driven approach ensures that member sync is reactive and automatic, r ## Default Configuration -### Defaults for New Release Tracks - -Newly created release tracks will use the following default member sync configuration: +Release tracks use the following default member sync configuration: ```javascript { @@ -640,24 +638,6 @@ The defaults were chosen to balance convenience with safety: 3. **`reset`** is the default because it's safer. New revisions might introduce issues that weren't present in the previous revision. Requiring re-review ensures that changes receive appropriate scrutiny. -### Migrating Existing Release Tracks - -Existing release tracks (created before this feature) will default to: - -```javascript -{ - member_sync: { - strategy: "manual", // Preserves backward-compatible behavior - supplant: { - behavior: "replace", - status_policy: "reset" - } - } -} -``` - -This ensures that existing workflows are not disrupted. Teams can opt into `track_latest` by explicitly updating their configuration. - --- ## API Reference @@ -874,7 +854,7 @@ config: { strategy: { type: String, enum: ["track_latest", "manual"], - default: "track_latest" // For new tracks + default: "track_latest" }, supplant: { behavior: { From 730430ccd013bba86b490bee7d19284073537689 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:39:05 -0500 Subject: [PATCH 198/370] feat(release-tracks): execute phase A of member sync strategy impl Updated schemas and snapshot service: release tracks will automatically receive the default member_sync config. The Update Config endpoint now accepts member_sync configuration updates --- .../release-tracks/release-track-schemas.js | 21 ++++++++++++ .../release-track-snapshot-schema.js | 33 +++++++++++++++++++ .../release-tracks/snapshot-service.js | 14 ++++++++ 3 files changed, 68 insertions(+) diff --git a/app/lib/release-tracks/release-track-schemas.js b/app/lib/release-tracks/release-track-schemas.js index 5580f00a..f4371d48 100644 --- a/app/lib/release-tracks/release-track-schemas.js +++ b/app/lib/release-tracks/release-track-schemas.js @@ -178,6 +178,21 @@ const conflictPolicySchema = z.enum([ 'abort', ]); +// Member sync schemas +const memberSyncStrategySchema = z.enum(['track_latest', 'manual']); +const memberSyncSupplantBehaviorSchema = z.enum(['replace', 'queue', 'ignore']); +const memberSyncStatusPolicySchema = z.enum(['reset', 'preserve']); + +const memberSyncSupplantSchema = z.object({ + behavior: memberSyncSupplantBehaviorSchema.optional(), + status_policy: memberSyncStatusPolicySchema.optional(), +}); + +const memberSyncConfigSchema = z.object({ + strategy: memberSyncStrategySchema.optional(), + supplant: memberSyncSupplantSchema.optional(), +}); + // ============================================================================= // Request body schemas (used inline by controller handlers) // ============================================================================= @@ -325,6 +340,7 @@ const updateConfigBodySchema = z.object({ auto_promote: z.boolean().optional(), include_candidates_in_snapshots: z.boolean().optional(), promotion_conflicts: promotionConflictsSchema.optional(), + member_sync: memberSyncConfigSchema.optional(), }); /** PUT /release-tracks/:id/composition */ @@ -367,6 +383,9 @@ module.exports = { deduplicationStrategySchema, resolutionStrategySchema, conflictPolicySchema, + memberSyncStrategySchema, + memberSyncSupplantBehaviorSchema, + memberSyncStatusPolicySchema, // Request body schemas createTrackBodySchema, @@ -390,4 +409,6 @@ module.exports = { snapshotScheduleSchema, objectRefEntrySchema, promotionConflictsSchema, + memberSyncConfigSchema, + memberSyncSupplantSchema, }; diff --git a/app/models/release-tracks/release-track-snapshot-schema.js b/app/models/release-tracks/release-track-snapshot-schema.js index 5b6d3fb9..208fbada 100644 --- a/app/models/release-tracks/release-track-snapshot-schema.js +++ b/app/models/release-tracks/release-track-snapshot-schema.js @@ -202,6 +202,35 @@ const includeSecondaryObjectsSchema = new mongoose.Schema(includeSecondaryObject _id: false, }); +// --- Member sync sub-schemas --- + +const memberSyncSupplantDefinition = { + behavior: { + type: String, + enum: ['replace', 'queue', 'ignore'], + default: 'replace', + }, + status_policy: { + type: String, + enum: ['reset', 'preserve'], + default: 'reset', + }, +}; +const memberSyncSupplantSchema = new mongoose.Schema(memberSyncSupplantDefinition, { _id: false }); + +const memberSyncDefinition = { + strategy: { + type: String, + enum: ['track_latest', 'manual'], + default: 'track_latest', + }, + supplant: { + type: memberSyncSupplantSchema, + default: () => ({}), + }, +}; +const memberSyncSchema = new mongoose.Schema(memberSyncDefinition, { _id: false }); + const configDefinition = { candidacy_threshold: { type: String, @@ -215,6 +244,10 @@ const configDefinition = { type: promotionConflictsSchema, default: () => ({}), }, + member_sync: { + type: memberSyncSchema, + default: () => ({}), + }, }; const configSchema = new mongoose.Schema(configDefinition, { _id: false }); diff --git a/app/services/release-tracks/snapshot-service.js b/app/services/release-tracks/snapshot-service.js index 8c93af26..92b551f8 100644 --- a/app/services/release-tracks/snapshot-service.js +++ b/app/services/release-tracks/snapshot-service.js @@ -429,6 +429,20 @@ exports.updateConfig = async function updateConfig(trackId, config, _userId) { ...config.promotion_conflicts, }; } + if (config.member_sync !== undefined) { + const existingMemberSync = existing.member_sync || {}; + mergedConfig.member_sync = { + ...existingMemberSync, + ...config.member_sync, + }; + // Nested merge for supplant sub-object + if (config.member_sync.supplant !== undefined) { + mergedConfig.member_sync.supplant = { + ...(existingMemberSync.supplant || {}), + ...config.member_sync.supplant, + }; + } + } return exports.cloneSnapshot(trackId, source, { config: mergedConfig }); }; From 867a32f42907c8ba1d512c7b55b37fc2fd552c6e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:33:12 -0500 Subject: [PATCH 199/370] feat(release-tracks): implement member sync strategies --- .../release-tracks/member-sync-service.js | 391 ++++++++++++++++++ .../release-tracks/release-tracks-service.js | 19 + 2 files changed, 410 insertions(+) create mode 100644 app/services/release-tracks/member-sync-service.js diff --git a/app/services/release-tracks/member-sync-service.js b/app/services/release-tracks/member-sync-service.js new file mode 100644 index 00000000..502ccb68 --- /dev/null +++ b/app/services/release-tracks/member-sync-service.js @@ -0,0 +1,391 @@ +'use strict'; + +// ============================================================================= +// Member Sync Service +// +// Handles automatic enrollment of new object revisions as candidates when +// the object is already a member of a release track. This service implements +// the "Member Sync Strategies" feature documented in 08_MEMBER_SYNC_STRATEGIES.md. +// +// Core functionality: +// - Listens for STIX object modification events via EventBus +// - Identifies release tracks where the modified object is a member +// - Applies the configured member sync strategy (track_latest vs manual) +// - Handles supplant behavior (replace/queue/ignore) +// - Creates new draft snapshots with auto-enrolled candidates +// +// This service is event-driven and operates independently of the main +// release track workflow. It integrates with workflow-service for +// auto-promotion after enrollment. +// +// Event Integration: +// Subscribes to BaseService CRUD events ({type}::created, {type}::updated) +// via the EventBus. When a STIX object is created or updated, this service +// checks if it's a member of any release track and auto-enrolls if configured. +// ============================================================================= + +const registryRepo = require('../../repository/release-tracks/release-track-registry.repository'); +const dynamicRepo = require('../../repository/release-tracks/release-track-dynamic.repository'); +const snapshotService = require('./snapshot-service'); +const workflowService = require('./workflow-service'); +const logger = require('../../lib/logger'); +const EventBus = require('../../lib/event-bus'); +const EventConstants = require('../../lib/event-constants'); + +// ============================================================================= +// Main entry point +// ============================================================================= + +/** + * Handle a STIX object modification event. + * + * Identifies release tracks where the object is a member and applies + * the configured member sync strategy to each. + * + * @param {Object} event - The modification event + * @param {string} event.objectRef - The STIX ID of the modified object + * @param {Date|string} event.newModified - The new modified timestamp + * @param {Date|string} [event.oldModified] - The previous modified timestamp (if update) + * @param {string} [event.modifiedBy] - User who made the modification + * @returns {Promise} Array of affected release track snapshots + */ +exports.handleObjectModified = async function handleObjectModified(event) { + const { objectRef, newModified, modifiedBy } = event; + + // 1. Find all release tracks where this object is in members + const affectedTracks = await findTracksWithObjectInMembers(objectRef); + + if (affectedTracks.length === 0) { + logger.debug(`[member-sync] No release tracks contain ${objectRef} in members`); + return []; + } + + logger.debug( + `[member-sync] Found ${affectedTracks.length} track(s) with ${objectRef} in members`, + ); + + // 2. Process each track according to its member_sync config + const results = []; + for (const trackInfo of affectedTracks) { + try { + const result = await processMemberSync(trackInfo.trackId, trackInfo.snapshot, { + objectRef, + newModified, + modifiedBy, + }); + if (result) results.push(result); + } catch (err) { + logger.error(`[member-sync] Error processing track ${trackInfo.trackId}: ${err.message}`); + // Continue processing other tracks; don't let one failure stop all + } + } + + return results; +}; + +// ============================================================================= +// Track discovery +// ============================================================================= + +/** + * Find all release tracks where the given object is in the members array. + * + * @param {string} objectRef - The STIX ID to search for + * @returns {Promise>} + */ +async function findTracksWithObjectInMembers(objectRef) { + // Get all track IDs from registry + const allTracks = await registryRepo.findAll({ limit: 10000 }); + const results = []; + + for (const trackInfo of allTracks.data) { + // Skip virtual tracks (they don't have the same member sync semantics) + if (trackInfo.type === 'virtual') continue; + + const snapshot = await dynamicRepo.getLatestSnapshot(trackInfo.track_id); + if (!snapshot) continue; + + // Check if object is in members + const memberEntry = snapshot.members?.find((m) => m.object_ref === objectRef); + if (memberEntry) { + results.push({ + trackId: trackInfo.track_id, + snapshot, + }); + } + } + + return results; +} + +// ============================================================================= +// Core sync logic +// ============================================================================= + +/** + * Process member sync for a single release track. + * + * Applies the configured strategy to determine if/how to enroll + * the new object revision as a candidate. + * + * @param {string} trackId - The release track ID + * @param {Object} snapshot - The current latest snapshot + * @param {Object} event - The modification event details + * @param {string} event.objectRef - STIX ID of the modified object + * @param {Date|string} event.newModified - New modified timestamp + * @param {string} [event.modifiedBy] - User who made the modification + * @returns {Promise} New snapshot if changes made, null otherwise + */ +async function processMemberSync(trackId, snapshot, event) { + const { objectRef, newModified, modifiedBy } = event; + + // Get member sync config with defaults + const config = getMemberSyncConfig(snapshot); + + // Check strategy + if (config.strategy === 'manual') { + logger.debug(`[member-sync] Track ${trackId} uses manual strategy, skipping auto-enrollment`); + return null; + } + + // strategy === 'track_latest' + // Check if object already exists in candidates or staged + const existingInCandidates = snapshot.candidates?.find((c) => c.object_ref === objectRef); + const existingInStaged = snapshot.staged?.find((s) => s.object_ref === objectRef); + const existingEntry = existingInStaged || existingInCandidates; + const existingTier = existingInStaged ? 'staged' : existingInCandidates ? 'candidates' : null; + + // Determine action based on supplant.behavior + let action = null; + if (!existingEntry) { + // No existing entry → simple enrollment + action = { type: 'enroll', tier: 'candidates' }; + } else { + // Existing entry → apply supplant behavior + switch (config.supplant.behavior) { + case 'replace': + action = { + type: 'replace', + removeTier: existingTier, + removeEntry: existingEntry, + targetTier: config.supplant.status_policy === 'preserve' ? existingTier : 'candidates', + }; + break; + case 'queue': + action = { type: 'enroll', tier: 'candidates' }; + break; + case 'ignore': + logger.debug( + `[member-sync] Track ${trackId}: ignoring ${objectRef} (existing entry in ${existingTier})`, + ); + return null; + } + } + + if (!action) return null; + + // Build the new candidate/staged entry + const now = new Date(); + const newEntry = { + object_ref: objectRef, + object_modified: new Date(newModified), + object_added_at: now, + object_added_by: modifiedBy || 'system', + }; + + // Determine status and tier placement + const targetTier = action.targetTier || action.tier; + + if (action.type === 'replace' && config.supplant.status_policy === 'preserve') { + // Preserve status from old entry + newEntry.object_status = action.removeEntry.object_status; + if (targetTier === 'staged') { + newEntry.object_staged_at = now; + newEntry.object_staged_by = modifiedBy || 'system'; + } + } else { + // Reset status to work-in-progress + newEntry.object_status = 'work-in-progress'; + } + + // Build updated tier arrays + let newCandidates = [...(snapshot.candidates || [])]; + let newStaged = [...(snapshot.staged || [])]; + + // Remove old entry if replacing + if (action.type === 'replace') { + if (action.removeTier === 'candidates') { + newCandidates = newCandidates.filter( + (c) => + !( + c.object_ref === objectRef && + new Date(c.object_modified).getTime() === + new Date(action.removeEntry.object_modified).getTime() + ), + ); + } else if (action.removeTier === 'staged') { + newStaged = newStaged.filter( + (s) => + !( + s.object_ref === objectRef && + new Date(s.object_modified).getTime() === + new Date(action.removeEntry.object_modified).getTime() + ), + ); + } + } + + // Add new entry to target tier + if (targetTier === 'staged') { + newStaged.push(newEntry); + } else { + newCandidates.push(newEntry); + } + + // Clone snapshot with updated tiers + const newSnapshot = await snapshotService.cloneSnapshot(trackId, snapshot, { + candidates: newCandidates, + staged: newStaged, + }); + + logger.info(`[member-sync] Track ${trackId}: ${action.type} ${objectRef} → ${targetTier}`); + + // Check if auto-promotion should occur (new entry in candidates that meets threshold) + if (targetTier === 'candidates' && snapshot.config?.auto_promote) { + const promoted = await workflowService.evaluateAutoPromotion(trackId, newSnapshot); + if (promoted) { + logger.info(`[member-sync] Track ${trackId}: auto-promoted ${objectRef} to staged`); + return promoted; + } + } + + return newSnapshot; +} + +// ============================================================================= +// Configuration helpers +// ============================================================================= + +/** + * Get the member sync configuration with defaults applied. + * + * @param {Object} snapshot - The snapshot to read config from + * @returns {Object} Member sync config with defaults + */ +function getMemberSyncConfig(snapshot) { + const memberSync = snapshot.config?.member_sync || {}; + + return { + strategy: memberSync.strategy || 'track_latest', + supplant: { + behavior: memberSync.supplant?.behavior || 'replace', + status_policy: memberSync.supplant?.status_policy || 'reset', + }, + }; +} + +// ============================================================================= +// Event Subscription +// ============================================================================= + +/** + * All STIX object event names that can trigger member sync. + * We listen to both ::created and ::updated events for each type. + */ +const STIX_OBJECT_EVENTS = [ + // Core ATT&CK objects + EventConstants.ATTACK_PATTERN_CREATED, + EventConstants.ATTACK_PATTERN_UPDATED, + EventConstants.TACTIC_CREATED, + EventConstants.TACTIC_UPDATED, + EventConstants.COURSE_OF_ACTION_CREATED, + EventConstants.COURSE_OF_ACTION_UPDATED, + EventConstants.INTRUSION_SET_CREATED, + EventConstants.INTRUSION_SET_UPDATED, + EventConstants.MALWARE_CREATED, + EventConstants.MALWARE_UPDATED, + EventConstants.TOOL_CREATED, + EventConstants.TOOL_UPDATED, + EventConstants.CAMPAIGN_CREATED, + EventConstants.CAMPAIGN_UPDATED, + EventConstants.DATA_SOURCE_CREATED, + EventConstants.DATA_SOURCE_UPDATED, + EventConstants.DATA_COMPONENT_CREATED, + EventConstants.DATA_COMPONENT_UPDATED, + EventConstants.MATRIX_CREATED, + EventConstants.MATRIX_UPDATED, + EventConstants.COLLECTION_CREATED, + EventConstants.COLLECTION_UPDATED, + EventConstants.ASSET_CREATED, + EventConstants.ASSET_UPDATED, + // Detection strategies and analytics + EventConstants.DETECTION_STRATEGY_CREATED, + EventConstants.DETECTION_STRATEGY_UPDATED, + EventConstants.ANALYTIC_CREATED, + EventConstants.ANALYTIC_UPDATED, +]; + +/** + * Handle a STIX object event from BaseService. + * + * Transforms the BaseService event payload into the format expected by + * handleObjectModified and triggers member sync processing. + * + * @param {Object} payload - Event payload from BaseService + * @param {string} payload.stixId - The STIX ID + * @param {Object} payload.document - The created/updated document + * @param {Object} [payload.previousDocument] - Previous document (for updates) + * @param {string} payload.type - The STIX type + * @param {Object} [payload.options] - Creation options (for created events) + */ +async function handleStixObjectEvent(payload) { + const { stixId, document, previousDocument, options } = payload; + + // Transform to member sync event format + const event = { + objectRef: stixId, + newModified: document.stix?.modified, + oldModified: previousDocument?.stix?.modified, + // Try to get user from options (create) or from document workflow metadata + modifiedBy: + options?.userAccountId || document.workspace?.workflow?.created_by_user_account || 'system', + }; + + try { + await exports.handleObjectModified(event); + } catch (err) { + logger.error(`[member-sync] Error handling object modification: ${err.message}`, err); + } +} + +/** + * Initialize event listeners for member sync. + * + * Subscribes to all STIX object created/updated events via the EventBus. + * Called automatically when this module is loaded. + */ +function initializeEventListeners() { + for (const eventName of STIX_OBJECT_EVENTS) { + EventBus.on(eventName, handleStixObjectEvent); + } + + logger.info( + `[member-sync] Member sync service initialized, listening to ${STIX_OBJECT_EVENTS.length} event types`, + ); +} + +// Self-initialize when module is loaded (follows pattern from analytics-service.js) +initializeEventListeners(); + +// ============================================================================= +// Exports for testing +// ============================================================================= + +// Expose internal functions for unit testing +exports._internal = { + findTracksWithObjectInMembers, + processMemberSync, + getMemberSyncConfig, + handleStixObjectEvent, + STIX_OBJECT_EVENTS, +}; diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index 5fc1c5c2..277f8ae8 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -22,6 +22,7 @@ const virtualTrackService = require('./virtual-track-service'); const exportService = require('./export-service'); const ephemeralService = require('./ephemeral-service'); const bundleImportService = require('./bundle-import-service'); +const memberSyncService = require('./member-sync-service'); const MODULE = 'release-tracks-service'; @@ -211,3 +212,21 @@ exports.previewVirtualSnapshot = function previewVirtualSnapshot(trackId) { exports.listObjectVersions = function listObjectVersions(trackId, objectRef) { return standardTrackService.listObjectVersions(trackId, objectRef); }; + +// ----------------------------------------------------------------------------- +// Member sync (Phase 7 → member-sync-service) +// +// Member sync auto-initializes when this module is loaded via the +// memberSyncService import. It subscribes to all STIX object created/updated +// events on the EventBus and automatically enrolls new object revisions +// as candidates when the object is a member of a release track. +// ----------------------------------------------------------------------------- + +/** + * Manually trigger member sync for a STIX object modification. + * Typically not needed since member-sync-service subscribes to EventBus events + * automatically. Useful for testing or manual re-processing. + */ +exports.handleObjectModified = function handleObjectModified(event) { + return memberSyncService.handleObjectModified(event); +}; From a20a783f62b2bea279a7e67c759a990848af9d65 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:34:40 -0500 Subject: [PATCH 200/370] fix(release-tracks): remove include_candidates_in_snapshots config setting --- .../definitions/components/release-tracks.yml | 4 --- .../release-tracks/release-track-schemas.js | 1 - .../release-track-snapshot-schema.js | 1 - .../release-tracks/snapshot-service.js | 2 -- docs/COLLECTIONS_V2/00_API_REFERENCE.md | 30 ++++++++++++++----- docs/COLLECTIONS_V2/06_ENTITIES.md | 1 - 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/api/definitions/components/release-tracks.yml b/app/api/definitions/components/release-tracks.yml index 31ae2de4..214d28ef 100644 --- a/app/api/definitions/components/release-tracks.yml +++ b/app/api/definitions/components/release-tracks.yml @@ -147,10 +147,6 @@ components: - reviewed description: 'Minimum workflow status required for auto-promotion to staged' default: 'reviewed' - include_candidates_in_snapshots: - type: boolean - description: 'Whether to include candidates when exporting snapshots' - default: false promotion_conflicts: type: object description: 'Conflict resolution policies for tier promotions' diff --git a/app/lib/release-tracks/release-track-schemas.js b/app/lib/release-tracks/release-track-schemas.js index f4371d48..3997c0f2 100644 --- a/app/lib/release-tracks/release-track-schemas.js +++ b/app/lib/release-tracks/release-track-schemas.js @@ -338,7 +338,6 @@ const promotionConflictsSchema = z.object({ const updateConfigBodySchema = z.object({ candidacy_threshold: candidacyThresholdSchema.optional(), auto_promote: z.boolean().optional(), - include_candidates_in_snapshots: z.boolean().optional(), promotion_conflicts: promotionConflictsSchema.optional(), member_sync: memberSyncConfigSchema.optional(), }); diff --git a/app/models/release-tracks/release-track-snapshot-schema.js b/app/models/release-tracks/release-track-snapshot-schema.js index 208fbada..bc16ceee 100644 --- a/app/models/release-tracks/release-track-snapshot-schema.js +++ b/app/models/release-tracks/release-track-snapshot-schema.js @@ -238,7 +238,6 @@ const configDefinition = { default: 'reviewed', }, auto_promote: { type: Boolean, default: true }, - include_candidates_in_snapshots: { type: Boolean, default: false }, include_secondary_objects: { type: includeSecondaryObjectsSchema, default: undefined }, promotion_conflicts: { type: promotionConflictsSchema, diff --git a/app/services/release-tracks/snapshot-service.js b/app/services/release-tracks/snapshot-service.js index 92b551f8..faa0ab4d 100644 --- a/app/services/release-tracks/snapshot-service.js +++ b/app/services/release-tracks/snapshot-service.js @@ -421,8 +421,6 @@ exports.updateConfig = async function updateConfig(trackId, config, _userId) { if (config.candidacy_threshold !== undefined) mergedConfig.candidacy_threshold = config.candidacy_threshold; if (config.auto_promote !== undefined) mergedConfig.auto_promote = config.auto_promote; - if (config.include_candidates_in_snapshots !== undefined) - mergedConfig.include_candidates_in_snapshots = config.include_candidates_in_snapshots; if (config.promotion_conflicts !== undefined) { mergedConfig.promotion_conflicts = { ...(existing.promotion_conflicts || {}), diff --git a/docs/COLLECTIONS_V2/00_API_REFERENCE.md b/docs/COLLECTIONS_V2/00_API_REFERENCE.md index 2c1770d8..2a82ddab 100644 --- a/docs/COLLECTIONS_V2/00_API_REFERENCE.md +++ b/docs/COLLECTIONS_V2/00_API_REFERENCE.md @@ -625,8 +625,7 @@ PUT /api/release-tracks/:id/config ```json { "candidacy_threshold": "work-in-progress" | "awaiting-review" | "reviewed", - "auto_promote": true | false, - "include_candidates_in_snapshots": true | false + "auto_promote": true | false } ``` @@ -634,6 +633,8 @@ PUT /api/release-tracks/:id/config ## Preview & Dry Run +> **Note on `include` Query Parameter:** The `include` query parameter (used on snapshot retrieval endpoints to filter which tiers are returned) is **NOT supported** on bump preview or dry-run operations. Bump previews and dry-runs are intended to show the user exactly what *will* happen when a bump occurs; ad-hoc filters would be misleading because they do not affect the actual release outcome. + ### Preview Next Release (Read-Only) Shows a verbose diff of what will change in the next tagged release without creating any data. @@ -931,14 +932,20 @@ GET /api/release-tracks/:id/snapshots/preview ## Query Variations -All `GET` endpoints for release tracks and snapshots support these query parameters: +### Snapshot Retrieval Endpoints + +The following retrieval endpoints support `include` and `format` query parameters: + +- `GET /api/release-tracks/:id` (get latest snapshot) +- `GET /api/release-tracks/:id/snapshots/:modified` (get specific snapshot) +- `GET /api/release-tracks/ephemeral/:domain` (get ephemeral bundle) **Include Parameter** (controls which tiers are returned): ``` GET /api/release-tracks/:id # Default: members only -GET /api/release-tracks/:id?include=staged # Include staged tier -GET /api/release-tracks/:id?include=candidates # Include candidates tier -GET /api/release-tracks/:id?include=all # Include all tiers +GET /api/release-tracks/:id?include=staged # Members and staged tiers +GET /api/release-tracks/:id?include=candidates # Members and candidates tiers +GET /api/release-tracks/:id?include=all # All tiers (members, staged, candidates) ``` **Format Parameter** (controls output format): @@ -951,4 +958,13 @@ GET /api/release-tracks/:id?format=workbench # Workbench format with m **Combined Example:** ``` GET /api/release-tracks/:id?include=all&format=workbench -``` \ No newline at end of file +``` + +### Bump Operations (Preview & Dry Run) + +The `include` query parameter is **NOT supported** on bump preview or dry-run endpoints: + +- `GET /api/release-tracks/:id/bump/preview` — only `format` is supported +- `POST /api/release-tracks/:id/bump` with `dry_run: true` — only `format` is supported (via request body) + +These endpoints are designed to show exactly what *will* happen during a release bump. Allowing ad-hoc tier filters would be misleading because they do not affect the actual release outcome. \ No newline at end of file diff --git a/docs/COLLECTIONS_V2/06_ENTITIES.md b/docs/COLLECTIONS_V2/06_ENTITIES.md index e325697a..0c374bf2 100644 --- a/docs/COLLECTIONS_V2/06_ENTITIES.md +++ b/docs/COLLECTIONS_V2/06_ENTITIES.md @@ -121,7 +121,6 @@ Each release track snapshot will be tracked as an individual MongoDB Document in config: { candidacy_threshold: "awaiting-review", // "work-in-progress" | "awaiting-review" | "reviewed" auto_promote: true, // Auto-promote reviewed objects to staged - include_candidates_in_snapshots: false, // Whether snapshots include candidates include_secondary_objects: { enabled: true, status_threshold: "reviewed" From 22d37a54cfcf4717d916dbd59f0f5afdbd426faf Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 16 Feb 2026 19:04:38 -0500 Subject: [PATCH 201/370] chore: update zod and adm versions --- package-lock.json | 1068 +++++++++++++++++++++++++++++++-------------- package.json | 4 +- 2 files changed, 739 insertions(+), 333 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d78a457..224c08fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.5.1", + "@mitre-attack/attack-data-model": "^4.9.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -43,7 +43,7 @@ "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", "winston": "^3.17.0", - "zod": "4.2.1" + "zod": "^4.3.6" }, "devDependencies": { "@codedependant/semantic-release-docker": "^5.1.1", @@ -271,47 +271,47 @@ } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.970.0.tgz", - "integrity": "sha512-1JTW7UFTMjv0U61bCMWnqLF3fIUcCAfrhZX33XcKMs1CchhdDTTn/IBfPJPD7RanyfjeuP7sOtEHNYqPqLFeJQ==", + "version": "3.991.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.991.0.tgz", + "integrity": "sha512-U3Qh+SPWQQiK+ON6fCOC+SctNlsigATTnz1TkD28l35j6fs7+QAEjbtP07LpfZ926mx3ibBN3jMRDFsOKSXwRQ==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-node": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.970.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-node": "^3.972.9", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.991.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -323,46 +323,46 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.970.0.tgz", - "integrity": "sha512-ArmgnOsSCXN5VyIvZb4kSP5hpqlRRHolrMtKQ/0N8Hw4MTb7/IeYHSZzVPNzzkuX6gn5Aj8txoUnDPM8O7pc9g==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.990.0.tgz", + "integrity": "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.970.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.990.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -373,22 +373,40 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/core": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.970.0.tgz", - "integrity": "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA==", + "version": "3.973.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.10.tgz", + "integrity": "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws-sdk/xml-builder": "3.969.0", - "@smithy/core": "^3.20.6", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/xml-builder": "^3.972.4", + "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", @@ -400,15 +418,15 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.970.0.tgz", - "integrity": "sha512-mZfK/fnmfHkbz1TktCnNKMxNdmbbBoa+Ywx9iKxO0dMHp1EMnuF+z31BIH5EEp2iYVW+R71lh97o1FtGTcATgw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.3.tgz", + "integrity": "sha512-dW/DqTk90XW7hIngqntAVtJJyrkS51wcLhGz39lOMe0TlSmZl+5R/UGnAZqNbXmWuJHLzxe+MLgagxH41aTsAQ==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/client-cognito-identity": "3.980.0", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -417,16 +435,86 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.980.0.tgz", + "integrity": "sha512-nLgMW2drTzv+dTo3ORCcotQPcrUaTQ+xoaDTdSaUXdZO7zbbVyk7ysE5GDTnJdZWcUjHOSB8xfNQhOTTNVPhFw==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.5", + "@aws-sdk/credential-provider-node": "^3.972.4", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.5", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.3", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.22.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.12", + "@smithy/middleware-retry": "^4.4.29", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.8", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.28", + "@smithy/util-defaults-mode-node": "^4.2.31", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-endpoints": { + "version": "3.980.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", + "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.970.0.tgz", - "integrity": "sha512-rtVzXzEtAfZBfh+lq3DAvRar4c3jyptweOAJR2DweyXx71QSMY+O879hjpMwES7jl07a3O1zlnFIDo4KP/96kQ==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.8.tgz", + "integrity": "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -436,22 +524,22 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.970.0.tgz", - "integrity": "sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.10.tgz", + "integrity": "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -459,22 +547,22 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.970.0.tgz", - "integrity": "sha512-L5R1hN1FY/xCmH65DOYMXl8zqCFiAq0bAq8tJZU32mGjIl1GzGeOkeDa9c461d81o7gsQeYzXyqFD3vXEbJ+kQ==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.8.tgz", + "integrity": "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-login": "3.970.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.970.0", - "@aws-sdk/credential-provider-web-identity": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-login": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", + "@aws-sdk/nested-clients": "3.990.0", + "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -485,17 +573,86 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/nested-clients": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.990.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-endpoints": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.970.0.tgz", - "integrity": "sha512-C+1dcLr+p2E+9hbHyvrQTZ46Kj4vC2RoP6N935GEukHQa637ZjXs8VlyHJ2xTvbvwwLZQNiu56Cx7o/OFOqw1A==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.8.tgz", + "integrity": "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -506,21 +663,90 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.990.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.970.0.tgz", - "integrity": "sha512-nMM0eeVuiLtw1taLRQ+H/H5Qp11rva8ILrzAQXSvlbDeVmbc7d8EeW5Q2xnCJu+3U+2JNZ1uxqIL22pB2sLEMA==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.9.tgz", + "integrity": "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-ini": "3.970.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.970.0", - "@aws-sdk/credential-provider-web-identity": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-ini": "^3.972.8", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", + "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -532,15 +758,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.970.0.tgz", - "integrity": "sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.8.tgz", + "integrity": "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -551,17 +777,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.970.0.tgz", - "integrity": "sha512-ROb+Aijw8nzkB14Nh2XRH861++SeTZykUzk427y8YtgTLxjAOjgDTchDUFW2Fx6GFWkSjqJ3sY7SZyb33IqyFw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.8.tgz", + "integrity": "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/client-sso": "3.970.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/token-providers": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/client-sso": "3.990.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/token-providers": "3.990.0", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -572,16 +798,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.970.0.tgz", - "integrity": "sha512-r7tnYJJg+B6QvnsRHSW5vDol+ks6n+5jBZdCFdGyK63hjcMRMqHx59zEH8O47UR1PFv5hS2Q3uGz6HXvVtP40Q==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.8.tgz", + "integrity": "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -591,29 +817,98 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/nested-clients": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.990.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/util-endpoints": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.970.0.tgz", - "integrity": "sha512-vBQJLwr1VSUD8jWgaS0nuWIGWXkUlfv+c/fXfYvgMWPFow9ShggGo/lfo/y4OC69mbWfMyScIxBVUp78/st9tA==", + "version": "3.991.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.991.0.tgz", + "integrity": "sha512-DlNG60+kVja4CtR5KPbbXFlNi2M4dGjKRnzN2aexb1ge2jqvK20vX2MGXyNj7oVNQcvwN/qUiCJ77vzS0H7miA==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.970.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-cognito-identity": "3.970.0", - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-ini": "3.970.0", - "@aws-sdk/credential-provider-login": "3.970.0", - "@aws-sdk/credential-provider-node": "3.970.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.970.0", - "@aws-sdk/credential-provider-web-identity": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/client-cognito-identity": "3.991.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", + "@aws-sdk/credential-provider-env": "^3.972.8", + "@aws-sdk/credential-provider-http": "^3.972.10", + "@aws-sdk/credential-provider-ini": "^3.972.8", + "@aws-sdk/credential-provider-login": "^3.972.8", + "@aws-sdk/credential-provider-node": "^3.972.9", + "@aws-sdk/credential-provider-process": "^3.972.8", + "@aws-sdk/credential-provider-sso": "^3.972.8", + "@aws-sdk/credential-provider-web-identity": "^3.972.8", + "@aws-sdk/nested-clients": "3.991.0", + "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.23.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", @@ -625,14 +920,14 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz", - "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", + "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -642,14 +937,14 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz", - "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", + "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -658,14 +953,14 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz", - "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", + "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", @@ -676,17 +971,17 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.970.0.tgz", - "integrity": "sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.10.tgz", + "integrity": "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@smithy/core": "^3.20.6", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.990.0", + "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -695,47 +990,65 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.970.0.tgz", - "integrity": "sha512-RIl8s4DCa31MXtRFw23iU90OqEoWuwQxiZOZshzsPtjyrunhHFjyZJEqb+vuQcYd1o22SMaYa3lPJRp64OH35Q==", + "version": "3.991.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.991.0.tgz", + "integrity": "sha512-vCWX2O4Kf9h0BviR46r2kc9cAv9twcxDCW9Rlszjkxg0+QN3ji0Q68OVfFZKZYx1BIPkPaWwjeMFB3iUtyyC3w==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.970.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.991.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -747,14 +1060,14 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz", - "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", + "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", @@ -765,16 +1078,16 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.970.0.tgz", - "integrity": "sha512-YO8KgJecxHIFMhfoP880q51VXFL9V1ELywK5yzVEqzyrwqoG93IUmnTygBUylQrfkbH+QqS0FxEdgwpP3fcwoQ==", + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.990.0.tgz", + "integrity": "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/nested-clients": "3.990.0", + "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", @@ -784,10 +1097,79 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/nested-clients": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz", + "integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.10", + "@aws-sdk/middleware-host-header": "^3.972.3", + "@aws-sdk/middleware-logger": "^3.972.3", + "@aws-sdk/middleware-recursion-detection": "^3.972.3", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/region-config-resolver": "^3.972.3", + "@aws-sdk/types": "^3.973.1", + "@aws-sdk/util-endpoints": "3.990.0", + "@aws-sdk/util-user-agent-browser": "^3.972.3", + "@aws-sdk/util-user-agent-node": "^3.972.8", + "@smithy/config-resolver": "^4.4.6", + "@smithy/core": "^3.23.0", + "@smithy/fetch-http-handler": "^5.3.9", + "@smithy/hash-node": "^4.2.8", + "@smithy/invalid-dependency": "^4.2.8", + "@smithy/middleware-content-length": "^4.2.8", + "@smithy/middleware-endpoint": "^4.4.14", + "@smithy/middleware-retry": "^4.4.31", + "@smithy/middleware-serde": "^4.2.9", + "@smithy/middleware-stack": "^4.2.8", + "@smithy/node-config-provider": "^4.3.8", + "@smithy/node-http-handler": "^4.4.10", + "@smithy/protocol-http": "^5.3.8", + "@smithy/smithy-client": "^4.11.3", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.30", + "@smithy/util-defaults-mode-node": "^4.2.33", + "@smithy/util-endpoints": "^3.2.8", + "@smithy/util-middleware": "^4.2.8", + "@smithy/util-retry": "^4.2.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/util-endpoints": { + "version": "3.990.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz", + "integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.1", + "@smithy/types": "^4.12.0", + "@smithy/url-parser": "^4.2.8", + "@smithy/util-endpoints": "^3.2.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", + "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", "license": "Apache-2.0", "optional": true, "peer": true, @@ -800,14 +1182,14 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.970.0.tgz", - "integrity": "sha512-TZNZqFcMUtjvhZoZRtpEGQAdULYiy6rcGiXAbLU7e9LSpIYlRqpLa207oMNfgbzlL2PnHko+eVg8rajDiSOYCg==", + "version": "3.991.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.991.0.tgz", + "integrity": "sha512-m8tcZ3SbqG3NRDv0Py3iBKdb4/FlpOCP4CQ6wRtsk4vs3UypZ0nFdZwCRVnTN7j+ldj+V72xVi/JBlxFBDE7Sg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", @@ -818,9 +1200,9 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.2.tgz", - "integrity": "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q==", + "version": "3.965.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.4.tgz", + "integrity": "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog==", "license": "Apache-2.0", "optional": true, "peer": true, @@ -832,29 +1214,29 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz", - "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", + "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", + "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.970.0.tgz", - "integrity": "sha512-TNQpwIVD6SxMwkD+QKnaujKVyXy5ljN3O3jrI7nCHJ3GlJu5xJrd8yuBnanYCcrn3e2zwdfOh4d4zJAZvvIvVw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.8.tgz", + "integrity": "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/types": "3.969.0", + "@aws-sdk/middleware-user-agent": "^3.972.10", + "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" @@ -872,15 +1254,15 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", - "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", + "version": "3.972.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", + "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", + "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" }, "engines": { @@ -899,9 +1281,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -1651,14 +2033,14 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.3.tgz", - "integrity": "sha512-XYtV7dFosqZNMs04VG+X1xVktjI0blZKrpXfyKXMb8bum0dSucRegTGHBZDYqDQLNbFdzEfSh9IH92QT8SiI/Q==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.9.0.tgz", + "integrity": "sha512-n/X02TnJcUEwsAL5JwTAs3Ad8AJJ0IP5SDQG7C9/DHcuqXsq2UwwsCIcO0/LBDqo7CH1Z+BI4hGDXEuDAJrtbw==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", "uuid": "^10.0.0", - "zod": "^4.0.5" + "zod": "^4.3.6" } }, "node_modules/@mitre-attack/attack-data-model/node_modules/uuid": { @@ -1675,9 +2057,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz", - "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==", + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -2344,9 +2726,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, "license": "MIT" }, @@ -2452,9 +2834,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.20.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.6.tgz", - "integrity": "sha512-BpAffW1mIyRZongoKBbh3RgHG+JDHJek/8hjA/9LnPunM+ejorO6axkxCgwxCe4K//g/JdPeR9vROHDYr/hfnQ==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.0.tgz", + "integrity": "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg==", "license": "Apache-2.0", "optional": true, "peer": true, @@ -2465,7 +2847,7 @@ "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -2573,14 +2955,14 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.7.tgz", - "integrity": "sha512-SCmhUG1UwtnEhF5Sxd8qk7bJwkj1BpFzFlHkXqKCEmDPLrRjJyTGM0EhqT7XBtDaDJjCfjRJQodgZcKDR843qg==", + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.14.tgz", + "integrity": "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@smithy/core": "^3.20.6", + "@smithy/core": "^3.23.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", @@ -2594,9 +2976,9 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.23", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.23.tgz", - "integrity": "sha512-lLEmkQj7I7oKfvZ1wsnToGJouLOtfkMXDKRA1Hi6F+mMp5O1N8GcVWmVeNgTtgZtd0OTXDTI2vpVQmeutydGew==", + "version": "4.4.31", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.31.tgz", + "integrity": "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg==", "license": "Apache-2.0", "optional": true, "peer": true, @@ -2604,7 +2986,7 @@ "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", @@ -2664,9 +3046,9 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", - "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz", + "integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==", "license": "Apache-2.0", "optional": true, "peer": true, @@ -2793,19 +3175,19 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.10.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.8.tgz", - "integrity": "sha512-wcr3UEL26k7lLoyf9eVDZoD1nNY3Fa1gbNuOXvfxvVWLGkOVW+RYZgUUp/bXHryJfycIOQnBq9o1JAE00ax8HQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.3.tgz", + "integrity": "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { - "@smithy/core": "^3.20.6", - "@smithy/middleware-endpoint": "^4.4.7", + "@smithy/core": "^3.23.0", + "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" }, "engines": { @@ -2916,15 +3298,15 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.22", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.22.tgz", - "integrity": "sha512-O2WXr6ZRqPnbyoepb7pKcLt1QL6uRfFzGYJ9sGb5hMJQi7v/4RjRmCQa9mNjA0YiXqsc5lBmLXqJPhjM1Vjv5A==", + "version": "4.3.30", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.30.tgz", + "integrity": "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -2933,9 +3315,9 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.25.tgz", - "integrity": "sha512-7uMhppVNRbgNIpyUBVRfjGHxygP85wpXalRvn9DvUlCx4qgy1AB/uxOPSiDx/jFyrwD3/BypQhx1JK7f3yxrAw==", + "version": "4.2.33", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.33.tgz", + "integrity": "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA==", "license": "Apache-2.0", "optional": true, "peer": true, @@ -2944,7 +3326,7 @@ "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.8", + "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, @@ -3014,15 +3396,15 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.10", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", - "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz", + "integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==", "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", + "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", @@ -3202,18 +3584,18 @@ "license": "MIT" }, "node_modules/@types/multer": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", - "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -3377,9 +3759,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3424,9 +3806,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { @@ -3557,20 +3939,20 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.4.tgz", + "integrity": "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -3688,9 +4070,9 @@ "license": "MIT" }, "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT", "optional": true, "peer": true @@ -4413,9 +4795,9 @@ } }, "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", "engines": { "node": ">=20" @@ -4733,9 +5115,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -4743,6 +5125,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cosmiconfig": { @@ -5870,13 +6256,13 @@ } }, "node_modules/express-openapi-validator": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.0.tgz", - "integrity": "sha512-gNaMgDb1cAT8QKcuh9WrED9p3mqi/V7yocNrvnE1fOz7e8p8JkbYaTUcOB4VsZKerz/X+Sey7ptTGF5FwsXh8Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.2.tgz", + "integrity": "sha512-fkDn4+ImUC4HTJ1g0cek/ItqYhmEO19AglJd2Iw2OJco0jLIbxIlDGVazmXbvvYeziU4Bnah2h+S2tb6NtWg8w==", "license": "MIT", "dependencies": { - "@apidevtools/json-schema-ref-parser": "^14.0.3", - "@types/multer": "^1.4.13", + "@apidevtools/json-schema-ref-parser": "^14.2.1", + "@types/multer": "^2.0.0", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", @@ -5887,8 +6273,8 @@ "media-typer": "^1.1.0", "multer": "^2.0.2", "ono": "^7.1.3", - "path-to-regexp": "^8.2.0", - "qs": "^6.14.0" + "path-to-regexp": "^8.3.0", + "qs": "^6.14.1" }, "peerDependencies": { "express": "*" @@ -5923,22 +6309,26 @@ } }, "node_modules/express-session": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", - "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", + "safe-buffer": "~5.2.1", "uid-safe": "~2.1.5" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session/node_modules/cookie": { @@ -6034,6 +6424,21 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/express/node_modules/raw-body": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", @@ -6136,9 +6541,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", "funding": [ { "type": "github", @@ -6670,6 +7075,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -7858,9 +8264,9 @@ } }, "node_modules/jwks-rsa": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.1.tgz", - "integrity": "sha512-r7QdN9TdqI6aFDFZt+GpAqj5yRtMUv23rL2I01i7B8P2/g8F0ioEN6VeSObKgTLs4GmmNJwP9J7Fyp/AYDBGRg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", + "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", "license": "MIT", "dependencies": { "@types/jsonwebtoken": "^9.0.4", @@ -7912,9 +8318,9 @@ } }, "node_modules/kruptein": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.1.7.tgz", - "integrity": "sha512-Wk3WFZdGtjnTBGlC12SiBsEh51+6lsW7sSRm+MzRVRc3WRPrUQGu4+/n3YQjSPp8x1AJJxPxgLsekFXLiBN2PQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.2.0.tgz", + "integrity": "sha512-Bcou7bKBn3k2ZEDXyYzR/j7YWWFDIcqv0ZeabHHPWW1aYmfLn0qmJJoWPVeQvh37g6vl2x3nEO9guBSzJsmuMQ==", "license": "MIT", "dependencies": { "asn1.js": "^5.4.1" @@ -8002,15 +8408,15 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", - "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "dev": true, "license": "MIT" }, @@ -8891,9 +9297,9 @@ } }, "node_modules/mongoose": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.21.0.tgz", - "integrity": "sha512-dW2U01gN8EVQT5KAO5AkzjbqWc8A/CsEq15jOzq/M9ISpy8jw3iq7W9ZP135h9zykFOMt3AMxq4+anvt2YNJgw==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.0.tgz", + "integrity": "sha512-Bul4Ha6J8IqzFrb0B1xpVzkC3S0sk43dmLSnhFOn8eJlZiLwL5WO6cRymmjaADdCMjUcCpj2ce8hZI6O4ZFSug==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -12746,9 +13152,9 @@ } }, "node_modules/prettier": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", - "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -12859,9 +13265,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -13341,9 +13747,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14211,9 +14617,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", - "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.1.tgz", + "integrity": "sha512-XdgQ8wkRGj1P0H0Vvo0TRMOQNz+8Q8J64/vcPOhxlaFx9eB3PYvHMXeyNrP46PXa9SUs/cg7OW/jm9U34KzUfA==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -14273,9 +14679,9 @@ } }, "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14359,9 +14765,9 @@ } }, "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -15255,9 +15661,9 @@ } }, "node_modules/zod": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", - "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 5f3d533d..35c7d53d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.5.1", + "@mitre-attack/attack-data-model": "^4.9.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -73,7 +73,7 @@ "swagger-ui-express": "^5.0.1", "uuid": "^11.1.0", "winston": "^3.17.0", - "zod": "4.2.1" + "zod": "^4.3.6" }, "devDependencies": { "@codedependant/semantic-release-docker": "^5.1.1", From 04638ec830eb368af5131bd6aa730c746665662c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:48:14 -0500 Subject: [PATCH 202/370] refactor: compose ADM schemas with base+checks to avoid Zod .check() restriction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zod v4.3.6+ disallows .omit(), .pick(), and .partial() on schemas that already have .check() applied. Restructure schema composition to apply operations in the safe order: base → .omit() → .partial() → .check(). - Import base schemas (without refinements) instead of full schemas - Add getSchema() to compose schemas at request time in the correct order - Simplify validateWorkspaceStixData() to accept STIX type strings - Remove unused createWorkspaceStixSchema and type extraction helpers - Remove ADM schema imports from all route files --- app/lib/validation-middleware.js | 264 +++++++++------------- app/lib/validation-schemas.js | 107 +++++++++ app/routes/analytics-routes.js | 6 +- app/routes/assets-routes.js | 6 +- app/routes/campaigns-routes.js | 6 +- app/routes/collections-routes.js | 4 +- app/routes/data-components-routes.js | 6 +- app/routes/data-sources-routes.js | 6 +- app/routes/detection-strategies-routes.js | 6 +- app/routes/groups-routes.js | 6 +- app/routes/identities-routes.js | 6 +- app/routes/matrices-routes.js | 6 +- app/routes/mitigations-routes.js | 6 +- app/routes/relationships-routes.js | 6 +- app/routes/software-routes.js | 5 +- app/routes/tactics-routes.js | 6 +- app/routes/techniques-routes.js | 6 +- package-lock.json | 8 +- package.json | 2 +- 19 files changed, 249 insertions(+), 219 deletions(-) create mode 100644 app/lib/validation-schemas.js diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 306966ad..d5c0641d 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -4,6 +4,7 @@ const { z, ZodError } = require('zod'); const { StatusCodes } = require('http-status-codes'); const logger = require('../lib/logger'); const { processValidationIssues } = require('../services/system/validate-service'); +const { STIX_SCHEMAS } = require('../lib/validation-schemas'); const { createAttackIdSchema, stixTypeToAttackIdMapping, @@ -57,111 +58,15 @@ function createWorkspaceSchema(stixType) { return workspaceSchema; } -function extractStringLiteralFromStixTypeZodSchema(zodSchema) { - // Method 1: Direct shape access (works for most schemas) - if (zodSchema.shape?.type?.def?.values?.[0]) { - return zodSchema.shape.type.def.values[0]; - } - // Method 2: Through _zod.def.in.def (works for schemas with .transform()) - else if (zodSchema._zod?.def?.in?.def?.shape?.type?.def?.values?.[0]) { - return zodSchema._zod.def.in.def.shape.type.def.values[0]; - } - // Method 3: Works for schemas that support multiple types, i.e., softwareSchema -> [tool, malware] - else if (zodSchema.shape?.type.def.options) { - const stixTypes = []; - for (const opt of zodSchema.shape.type.def.options) { - stixTypes.push(opt.def.values[0]); - } - return stixTypes; - } else { - throw new Error('Could not extract STIX type from schema'); - } -} - -/** - * Factory function that creates a combined workspace+STIX schema with conditional partial validation - * @param {z.ZodObject} stixSchema - The STIX object schema to validate against - * @param {string} workflowState - The workflow state to determine validation strictness - * @param {string[]} omitStixFields - Array of STIX field names to omit from validation - * @returns {z.ZodObject} Combined schema with workspace and conditional stix validation - */ -/** - * Factory function that creates a combined workspace+STIX schema with conditional partial validation - * @param {z.ZodObject} stixSchema - The STIX object schema to validate against - * @param {string} workflowState - The workflow state to determine validation strictness - * @param {string[]} omitStixFields - Array of STIX field names to omit from validation - * @returns {z.ZodObject} Combined schema with workspace and conditional stix validation - */ -function createWorkspaceStixSchema( - stixSchema, - workflowState, - omitStixFields = ['x_mitre_attack_spec_version', 'external_references'], -) { - logger.debug('Creating combined workspace+STIX schema:', { workflowState, omitStixFields }); - - try { - // Extract the STIX type from the schema with fallback for transformed schemas - const stixTypeStringLiteral = extractStringLiteralFromStixTypeZodSchema(stixSchema); - - logger.debug('Extracted STIX type from schema:', { stixTypeStringLiteral }); - - // Apply partial validation for work-in-progress, full validation otherwise - const usePartialValidation = workflowState === 'work-in-progress'; - logger.debug('Validation mode:', { workflowState, usePartialValidation }); - - let stixValidationSchema = usePartialValidation ? stixSchema.partial() : stixSchema; - - // Build omit object from array of field names - if (omitStixFields.length > 0) { - const omitObject = omitStixFields.reduce((acc, field) => { - acc[field] = true; - return acc; - }, {}); - stixValidationSchema = stixValidationSchema.omit(omitObject); - } - - const combinedSchema = z.object({ - workspace: createWorkspaceSchema(stixTypeStringLiteral), - stix: stixValidationSchema, - }); - - logger.debug('Successfully created combined schema'); - return combinedSchema; - } catch (error) { - logger.warn('Could not extract STIX type from schema, using basic validation:', { - error: error.message, - workflowState, - omitStixFields, - }); - - let stixValidationSchema = - workflowState === 'work-in-progress' ? stixSchema.partial() : stixSchema; - - // Apply omit in error case as well - if (omitStixFields.length > 0) { - const omitObject = omitStixFields.reduce((acc, field) => { - acc[field] = true; - return acc; - }, {}); - stixValidationSchema = stixValidationSchema.omit(omitObject); - } - - return z.object({ - workspace: workspaceSchema, - stix: stixValidationSchema, - }); - } -} - /** - * Middleware for parsing the request body using a specified STIX schema from the ATT&CK Data Model. - * Both the `workspace` and `stix` keys are checked. - * @param {z.ZodObject|z.ZodObject[]} oneOrMoreZodSchemas - Single schema or array of schemas to validate against + * Middleware for validating the request body against a pre-composed STIX schema. + * Wraps the STIX schema with a workspace schema and parses the request body. + * @param {z.ZodObject} stixSchema - Pre-composed STIX schema (with omit/partial/checks already applied) * @param {Object} options - Configuration options * @param {boolean} options.enabled - Whether validation is enabled (defaults to true) * @returns {Function} Express middleware function */ -function middleware(oneOrMoreZodSchemas, options = {}) { +function middleware(stixSchema, options = {}) { const { enabled = true } = options; return (req, res, next) => { @@ -181,59 +86,13 @@ function middleware(oneOrMoreZodSchemas, options = {}) { }); try { - // Extract workflow state from request body - const workflowState = req.body?.workspace?.workflow?.state || 'reviewed'; // Default to strict validation - logger.debug('Determined workflow state:', { - workflowState, - isDefault: !req.body?.workspace?.workflow?.state, - }); + const stixType = req.body?.stix?.type; - // Determine which schema to use based on request STIX type - const requestStixType = req.body?.stix?.type; - logger.debug('Request STIX type:', { requestStixType }); - - let finalSchema; - - // Handle array of schemas - find the one that matches the request type - if (Array.isArray(oneOrMoreZodSchemas)) { - logger.debug('Multiple schemas provided, finding matching schema for request type'); - - for (const schema of oneOrMoreZodSchemas) { - try { - const schemaStixType = extractStringLiteralFromStixTypeZodSchema(schema); - logger.debug('Checking schema with type:', { schemaStixType }); - - // Check if this schema matches the request type - if ( - (typeof schemaStixType === 'string' && schemaStixType === requestStixType) || - (Array.isArray(schemaStixType) && schemaStixType.includes(requestStixType)) - ) { - logger.debug('Found matching schema for request type:', { - requestStixType, - schemaStixType, - }); - finalSchema = schema; - break; - } - } catch (error) { - logger.debug('Could not extract type from schema, skipping:', { error: error.message }); - continue; - } - } - - if (!finalSchema) { - throw new Error( - `No matching schema found for STIX type: ${requestStixType}. Available schemas: ${oneOrMoreZodSchemas.length}`, - ); - } - } else { - // Single schema - use it directly - logger.debug('Single schema provided, using directly'); - finalSchema = oneOrMoreZodSchemas; - } - - // Create schema with conditional validation based on workflow state - const combinedSchema = createWorkspaceStixSchema(finalSchema, workflowState); + // Wrap the pre-composed STIX schema with the workspace schema + const combinedSchema = z.object({ + workspace: createWorkspaceSchema(stixType), + stix: stixSchema, + }); logger.debug('Attempting to parse request body with combined schema'); combinedSchema.parse(req.body); @@ -300,16 +159,111 @@ function middleware(oneOrMoreZodSchemas, options = {}) { }; } +/** + * Get the schema to use for validating a STIX object. + * + * Some STIX types define both a "base" schema and "checks" (refinements), + * while others only define a single schema (no refinements). This helper + * composes the correct schema based on the STIX type and workflow status. + * + * Composition order (for schemas with checks): + * base → .omit() → .partial() (if WIP) → .check(checks) + * + * This ordering is critical because Zod v4.3.6+ disallows .omit(), .pick(), + * and .partial() on schemas that already have .check() applied. + * + * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") + * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") + * @param {string[]} omitStixFields - Array of STIX field names to omit from validation + * @returns {Object|null} Zod schema, or null if the STIX type is unknown + */ +function getSchema( + stixType, + status, + omitStixFields = ['x_mitre_attack_spec_version', 'external_references'], +) { + const admSchemaRef = STIX_SCHEMAS[stixType]; + if (!admSchemaRef) return null; + + const isPartial = status === 'work-in-progress'; + let stixSchema; + + if (admSchemaRef.base && admSchemaRef.checks) { + // Schema with refinements: compose in the safe order (omit/partial BEFORE check) + stixSchema = admSchemaRef.base; + + if (omitStixFields.length > 0) { + const omitObject = omitStixFields.reduce((acc, field) => { + acc[field] = true; + return acc; + }, {}); + stixSchema = stixSchema.omit(omitObject); + } + + if (isPartial) { + stixSchema = stixSchema.partial(); + } + + // Re-apply refinements last + stixSchema = stixSchema.check(admSchemaRef.checks); + } else { + // Simple schema (no refinements): safe to call .omit() and .partial() directly + stixSchema = admSchemaRef; + + if (omitStixFields.length > 0) { + const omitObject = omitStixFields.reduce((acc, field) => { + acc[field] = true; + return acc; + }, {}); + stixSchema = stixSchema.omit(omitObject); + } + + if (isPartial) { + stixSchema = stixSchema.partial(); + } + } + + logger.debug('Resolved STIX schema:', { stixType, status, isPartial, omitStixFields }); + return stixSchema; +} + /** * Pre-configured validation middleware factory that uses runtime configuration. * The middleware reads the config value at request time to support dynamic config changes (e.g., during tests). + * + * @param {string|string[]} expectedStixType - The STIX type(s) this endpoint accepts + * (e.g. "attack-pattern" or ["tool", "malware"] for software) + * @returns {Function} Express middleware function */ -function validateWorkspaceStixData(oneOrMoreZodSchemas) { +function validateWorkspaceStixData(expectedStixType) { + const allowedTypes = Array.isArray(expectedStixType) ? expectedStixType : [expectedStixType]; + return (req, res, next) => { // Read config at request time to allow dynamic changes const config = require('../config/config'); const enabled = config.validateRequests.withAttackDataModel; - const middlewareFn = middleware(oneOrMoreZodSchemas, { enabled }); + const requestStixType = req.body?.stix?.type; + const workflowState = req.body?.workspace?.workflow?.state || 'reviewed'; + + // Verify the request's STIX type is one this endpoint accepts + if (!allowedTypes.includes(requestStixType)) { + return next( + new Error( + `Unexpected STIX type "${requestStixType}". This endpoint accepts: ${allowedTypes.join(', ')}`, + ), + ); + } + + const finalSchema = getSchema(requestStixType, workflowState); + if (!finalSchema) { + return next( + new Error( + `No schema found for STIX type "${requestStixType}". Request body is probably invalid.`, + ), + ); + } + + const middlewareFn = middleware(finalSchema, { enabled }); return middlewareFn(req, res, next); }; } @@ -317,8 +271,6 @@ function validateWorkspaceStixData(oneOrMoreZodSchemas) { module.exports = { /** Express middleware factory for workspace+STIX validation */ validateWorkspaceStixData, - /** Factory function for creating combined workspace+STIX schemas */ - createWorkspaceStixSchema, /** Basic workspace schema without dynamic attackId validation */ workspaceSchema, }; diff --git a/app/lib/validation-schemas.js b/app/lib/validation-schemas.js new file mode 100644 index 00000000..7b8e402a --- /dev/null +++ b/app/lib/validation-schemas.js @@ -0,0 +1,107 @@ +'use strict'; + +const { + tacticSchema, + + /** techniques */ + techniqueBaseSchema, + + /** groups */ + groupBaseSchema, + + /** malware */ + malwareBaseSchema, + + /** tools */ + toolBaseSchema, + + /** campaigns */ + campaignBaseSchema, + + /** relationships */ + relationshipBaseSchema, + + /** simple schemas (no checks/refinements) */ + identitySchema, + mitigationSchema, + assetSchema, + dataSourceSchema, + dataComponentSchema, + detectionStrategySchema, + analyticSchema, + matrixSchema, + collectionSchema, + markingDefinitionSchema, +} = require('@mitre-attack/attack-data-model/dist'); + +// The ADM exports bundles of refinements (checks) for any schemas which support partial schema derivatives. +// e.g., The technique.schema module exports: (1) techniqueBaseSchema, (2) techniquePartialSchema, (3) techniqueChecks +// +// This enables users to easily compose custom schemas w/o running into the Zod restriction introduced in v4.3.6 where +// `.omit`, `.pick`, and `.partial` throw when `.check` is chained on. +// (Details: https://github.com/mitre-attack/attack-data-model/pull/65) +// +// In Workbench, this is specifically necessary because the ADM validation middleware needs to omit checking fields +// which only the backend sets, e.g., `x_mitre_attack_spec_version` is set by the backend, therefore it's never passed/set in +// the req.body of POST/create requests, therefore we need to avoid scrutinizing that field in the ADM validation middleware. +// +// Composition order (for schemas with checks): +// base schema → .omit() → .partial() (if WIP) → .check(checks) +// This ensures .omit() and .partial() are called BEFORE .check(), avoiding the Zod restriction. + +const { + techniqueChecks, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/technique.schema'); +const { groupChecks } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/group.schema'); +const { + campaignChecks, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/campaign.schema'); +const { + relationshipChecks, +} = require('@mitre-attack/attack-data-model/dist/schemas/sro/relationship.schema'); +const { + malwareChecks, +} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/malware.schema'); +const { toolChecks } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/tool.schema'); + +const STIX_SCHEMAS = { + 'x-mitre-tactic': tacticSchema, + 'attack-pattern': { + base: techniqueBaseSchema, + checks: techniqueChecks, + }, + 'intrusion-set': { + base: groupBaseSchema, + checks: groupChecks, + }, + malware: { + base: malwareBaseSchema, + checks: malwareChecks, + }, + tool: { + base: toolBaseSchema, + checks: toolChecks, + }, + campaign: { + base: campaignBaseSchema, + checks: campaignChecks, + }, + relationship: { + base: relationshipBaseSchema, + checks: relationshipChecks, + }, + identity: identitySchema, + 'course-of-action': mitigationSchema, + 'marking-definition': markingDefinitionSchema, + 'x-mitre-asset': assetSchema, + 'x-mitre-data-source': dataSourceSchema, + 'x-mitre-data-component': dataComponentSchema, + 'x-mitre-detection-strategy': detectionStrategySchema, + 'x-mitre-analytic': analyticSchema, + 'x-mitre-matrix': matrixSchema, + 'x-mitre-collection': collectionSchema, +}; + +module.exports = { + STIX_SCHEMAS, +}; diff --git a/app/routes/analytics-routes.js b/app/routes/analytics-routes.js index a20c0a00..ea595023 100644 --- a/app/routes/analytics-routes.js +++ b/app/routes/analytics-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { analyticSchema } = require('@mitre-attack/attack-data-model'); - const analyticsController = require('../controllers/analytics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(analyticSchema), + validateWorkspaceStixData('x-mitre-analytic'), analyticsController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(analyticSchema), + validateWorkspaceStixData('x-mitre-analytic'), analyticsController.updateFull, ) .delete( diff --git a/app/routes/assets-routes.js b/app/routes/assets-routes.js index 3bf477e7..f7973ab6 100644 --- a/app/routes/assets-routes.js +++ b/app/routes/assets-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { assetSchema } = require('@mitre-attack/attack-data-model'); - const assetsController = require('../controllers/assets-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(assetSchema), + validateWorkspaceStixData('x-mitre-asset'), assetsController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(assetSchema), + validateWorkspaceStixData('x-mitre-asset'), assetsController.updateFull, ) .delete(authn.authenticate, authz.requireRole(authz.admin), assetsController.deleteVersionById); diff --git a/app/routes/campaigns-routes.js b/app/routes/campaigns-routes.js index f51c14d6..395006ff 100644 --- a/app/routes/campaigns-routes.js +++ b/app/routes/campaigns-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { campaignSchema } = require('@mitre-attack/attack-data-model'); - const campaignsController = require('../controllers/campaigns-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(campaignSchema), + validateWorkspaceStixData('campaign'), campaignsController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(campaignSchema), + validateWorkspaceStixData('campaign'), campaignsController.updateFull, ) .delete( diff --git a/app/routes/collections-routes.js b/app/routes/collections-routes.js index 60ce9fdb..487c7688 100644 --- a/app/routes/collections-routes.js +++ b/app/routes/collections-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { collectionSchema } = require('@mitre-attack/attack-data-model'); - const collectionsController = require('../controllers/collections-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(collectionSchema), + validateWorkspaceStixData('x-mitre-collection'), collectionsController.create, ); diff --git a/app/routes/data-components-routes.js b/app/routes/data-components-routes.js index 33004168..07c6f381 100644 --- a/app/routes/data-components-routes.js +++ b/app/routes/data-components-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { dataComponentSchema } = require('@mitre-attack/attack-data-model'); - const dataComponentsController = require('../controllers/data-components-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(dataComponentSchema), + validateWorkspaceStixData('x-mitre-data-component'), dataComponentsController.create, ); @@ -60,7 +58,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(dataComponentSchema), + validateWorkspaceStixData('x-mitre-data-component'), dataComponentsController.updateFull, ) .delete( diff --git a/app/routes/data-sources-routes.js b/app/routes/data-sources-routes.js index c76c3dc4..6b6624b3 100644 --- a/app/routes/data-sources-routes.js +++ b/app/routes/data-sources-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { dataSourceSchema } = require('@mitre-attack/attack-data-model'); - const dataSourcesController = require('../controllers/data-sources-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(dataSourceSchema), + validateWorkspaceStixData('x-mitre-data-source'), dataSourcesController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(dataSourceSchema), + validateWorkspaceStixData('x-mitre-data-source'), dataSourcesController.updateFull, ) .delete( diff --git a/app/routes/detection-strategies-routes.js b/app/routes/detection-strategies-routes.js index 823c9cae..81a3239e 100644 --- a/app/routes/detection-strategies-routes.js +++ b/app/routes/detection-strategies-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { detectionStrategySchema } = require('@mitre-attack/attack-data-model'); - const detectionStrategiesController = require('../controllers/detection-strategies-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -22,7 +20,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(detectionStrategySchema), + validateWorkspaceStixData('x-mitre-detection-strategy'), detectionStrategiesController.create, ); @@ -49,7 +47,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(detectionStrategySchema), + validateWorkspaceStixData('x-mitre-detection-strategy'), detectionStrategiesController.updateFull, ) .delete( diff --git a/app/routes/groups-routes.js b/app/routes/groups-routes.js index dd82c97e..e6ebd122 100644 --- a/app/routes/groups-routes.js +++ b/app/routes/groups-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { groupSchema } = require('@mitre-attack/attack-data-model'); - const groupsController = require('../controllers/groups-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -22,7 +20,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(groupSchema), + validateWorkspaceStixData('intrusion-set'), groupsController.create, ); @@ -45,7 +43,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(groupSchema), + validateWorkspaceStixData('intrusion-set'), groupsController.updateFull, ) .delete(authn.authenticate, authz.requireRole(authz.admin), groupsController.deleteVersionById); diff --git a/app/routes/identities-routes.js b/app/routes/identities-routes.js index 0c0fa80f..c8f5ded2 100644 --- a/app/routes/identities-routes.js +++ b/app/routes/identities-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { identitySchema } = require('@mitre-attack/attack-data-model'); - const identitiesController = require('../controllers/identities-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(identitySchema), + validateWorkspaceStixData('identity'), identitiesController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(identitySchema), + validateWorkspaceStixData('identity'), identitiesController.updateFull, ) .delete( diff --git a/app/routes/matrices-routes.js b/app/routes/matrices-routes.js index 7682418d..1801a2a9 100644 --- a/app/routes/matrices-routes.js +++ b/app/routes/matrices-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { matrixSchema } = require('@mitre-attack/attack-data-model'); - const matricesController = require('../controllers/matrices-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -22,7 +20,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(matrixSchema), + validateWorkspaceStixData('x-mitre-matrix'), matricesController.create, ); @@ -45,7 +43,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(matrixSchema), + validateWorkspaceStixData('x-mitre-matrix'), matricesController.updateFull, ) .delete(authn.authenticate, authz.requireRole(authz.admin), matricesController.deleteVersionById); diff --git a/app/routes/mitigations-routes.js b/app/routes/mitigations-routes.js index 9d678f31..e508253d 100644 --- a/app/routes/mitigations-routes.js +++ b/app/routes/mitigations-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { mitigationSchema } = require('@mitre-attack/attack-data-model'); - const mitigationsController = require('../controllers/mitigations-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(mitigationSchema), + validateWorkspaceStixData('course-of-action'), mitigationsController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(mitigationSchema), + validateWorkspaceStixData('course-of-action'), mitigationsController.updateFull, ) .delete( diff --git a/app/routes/relationships-routes.js b/app/routes/relationships-routes.js index 005cc5c5..0fce7577 100644 --- a/app/routes/relationships-routes.js +++ b/app/routes/relationships-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { relationshipSchema } = require('@mitre-attack/attack-data-model'); - const relationshipsController = require('../controllers/relationships-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(relationshipSchema), + validateWorkspaceStixData('relationship'), relationshipsController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(relationshipSchema), + validateWorkspaceStixData('relationship'), relationshipsController.updateFull, ) .delete( diff --git a/app/routes/software-routes.js b/app/routes/software-routes.js index 6f65040f..539cc1d7 100644 --- a/app/routes/software-routes.js +++ b/app/routes/software-routes.js @@ -1,7 +1,6 @@ 'use strict'; const express = require('express'); -const { toolSchema, malwareSchema } = require('@mitre-attack/attack-data-model'); const softwareController = require('../controllers/software-controller'); const authn = require('../lib/authn-middleware'); @@ -20,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData([toolSchema, malwareSchema]), + validateWorkspaceStixData(['tool', 'malware']), softwareController.create, ); @@ -43,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData([toolSchema, malwareSchema]), + validateWorkspaceStixData(['tool', 'malware']), softwareController.updateFull, ) .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteVersionById); diff --git a/app/routes/tactics-routes.js b/app/routes/tactics-routes.js index 834006c1..ff2a9df3 100644 --- a/app/routes/tactics-routes.js +++ b/app/routes/tactics-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { tacticSchema } = require('@mitre-attack/attack-data-model'); - const tacticsController = require('../controllers/tactics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(tacticSchema), + validateWorkspaceStixData('x-mitre-tactic'), tacticsController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(tacticSchema), + validateWorkspaceStixData('x-mitre-tactic'), tacticsController.updateFull, ) .delete(authn.authenticate, authz.requireRole(authz.admin), tacticsController.deleteVersionById); diff --git a/app/routes/techniques-routes.js b/app/routes/techniques-routes.js index 6707df83..4e406ef8 100644 --- a/app/routes/techniques-routes.js +++ b/app/routes/techniques-routes.js @@ -2,8 +2,6 @@ const express = require('express'); -const { techniqueSchema } = require('@mitre-attack/attack-data-model'); - const techniquesController = require('../controllers/techniques-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); @@ -21,7 +19,7 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(techniqueSchema), + validateWorkspaceStixData('attack-pattern'), techniquesController.create, ); @@ -44,7 +42,7 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(techniqueSchema), + validateWorkspaceStixData('attack-pattern'), techniquesController.updateFull, ) .delete( diff --git a/package-lock.json b/package-lock.json index 224c08fa..e2ab26a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.9.0", + "@mitre-attack/attack-data-model": "^4.10.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -2033,9 +2033,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.9.0.tgz", - "integrity": "sha512-n/X02TnJcUEwsAL5JwTAs3Ad8AJJ0IP5SDQG7C9/DHcuqXsq2UwwsCIcO0/LBDqo7CH1Z+BI4hGDXEuDAJrtbw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.10.0.tgz", + "integrity": "sha512-whLa1X8GURoNvKgpbAqZe2EymL9I5/ncbdRSDKgfYeYkt0iu3FwfzKVIlcq2sn5gKZ68o94kxSM5TDsi+2r6PA==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index 35c7d53d..feb4b86b 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.9.0", + "@mitre-attack/attack-data-model": "^4.10.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", From 58e556c780f5d1ec849e72a0735c064ae7168f4e Mon Sep 17 00:00:00 2001 From: adpare Date: Tue, 17 Feb 2026 23:56:40 -0500 Subject: [PATCH 203/370] fix: update tests to incorporate new schema structure --- app/lib/validation-middleware.js | 8 +++++--- app/tests/middleware/adm-validation-middleware.spec.js | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index d5c0641d..15c5abc8 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -9,7 +9,9 @@ const { createAttackIdSchema, stixTypeToAttackIdMapping, } = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-id'); - +const { + BadRequestError, +} = require('../exceptions'); /** * Basic workspace schema (without rigid attack ID validation) * @type {z.ZodObject} @@ -248,7 +250,7 @@ function validateWorkspaceStixData(expectedStixType) { // Verify the request's STIX type is one this endpoint accepts if (!allowedTypes.includes(requestStixType)) { return next( - new Error( + new BadRequestError( `Unexpected STIX type "${requestStixType}". This endpoint accepts: ${allowedTypes.join(', ')}`, ), ); @@ -257,7 +259,7 @@ function validateWorkspaceStixData(expectedStixType) { const finalSchema = getSchema(requestStixType, workflowState); if (!finalSchema) { return next( - new Error( + new BadRequestError( `No schema found for STIX type "${requestStixType}". Request body is probably invalid.`, ), ); diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 5effd4d5..8f13211b 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -325,8 +325,7 @@ describe('ADM Validation Middleware', function () { // Should fail validation expect(res.status).toBe(400); - expect(res.body.error).toBeDefined(); - expect(res.body.details).toBeDefined(); + expect(res.body.message).toBeDefined(); }); }); From 3b5e8f09aa6346be16e9d013a5abacb986e6673e Mon Sep 17 00:00:00 2001 From: adpare Date: Tue, 17 Feb 2026 23:57:13 -0500 Subject: [PATCH 204/370] chore: fix identation --- app/lib/validation-middleware.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 15c5abc8..240347a9 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -9,9 +9,7 @@ const { createAttackIdSchema, stixTypeToAttackIdMapping, } = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-id'); -const { - BadRequestError, -} = require('../exceptions'); +const { BadRequestError } = require('../exceptions'); /** * Basic workspace schema (without rigid attack ID validation) * @type {z.ZodObject} From bf510689a7a862ea1d78eb907baf68d5773a2f99 Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 18 Feb 2026 11:55:55 -0500 Subject: [PATCH 205/370] feat: add source and target objects to parallel relationships --- app/repository/relationships-repository.js | 5 ++++- app/services/reports-service.js | 25 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/repository/relationships-repository.js b/app/repository/relationships-repository.js index 40e15176..45a2495d 100644 --- a/app/repository/relationships-repository.js +++ b/app/repository/relationships-repository.js @@ -130,7 +130,10 @@ class RelationshipsRepository extends BaseRepository { } async retrieveParallelRelationships() { - const all_relationships = await this.retrieveAll({ versions: 'latest' }); + const all_relationships = await this.retrieveAll({ + versions: 'latest', + lookupRefs: true + }); // Create a mapping of rel_key (source_ref--relationship_type--target_ref) // to an array of relationships that share it. diff --git a/app/services/reports-service.js b/app/services/reports-service.js index 0774a786..af75e6e3 100644 --- a/app/services/reports-service.js +++ b/app/services/reports-service.js @@ -45,11 +45,34 @@ class ReportsService { * target_ref, and relationship_type. * @returns {Promise} Map of relationship keys to arrays of parallel relationships */ - async getParallelRelationships() { + async getParallelRelationships(options = {lookupRefs: true}) { const relationshipMap = await relationshipsRepository.retrieveParallelRelationships(); // Add identity information to each relationship in the map for (const relationships of relationshipMap.values()) { + // Get source and target objects + if (options.lookupRefs) { + for (const document of relationships) { + if (Array.isArray(document.source_objects)) { + if (document.source_objects.length === 0) { + document.source_objects = undefined; + } else { + document.source_objects.sort((a, b) => b.stix.modified - a.stix.modified); + document.source_object = document.source_objects[0]; + document.source_objects = undefined; + } + } + if (Array.isArray(document.target_objects)) { + if (document.target_objects.length === 0) { + document.target_objects = undefined; + } else { + document.target_objects.sort((a, b) => b.stix.modified - a.stix.modified); + document.target_object = document.target_objects[0]; + document.target_objects = undefined; + } + } + } + } await identitiesService.addCreatedByAndModifiedByIdentitiesToAll(relationships); } From df93627eec7fd98f28101d729db6afcdba951004 Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 18 Feb 2026 11:56:34 -0500 Subject: [PATCH 206/370] chore: fix identation --- app/repository/relationships-repository.js | 2 +- app/services/reports-service.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/repository/relationships-repository.js b/app/repository/relationships-repository.js index 45a2495d..5bbba914 100644 --- a/app/repository/relationships-repository.js +++ b/app/repository/relationships-repository.js @@ -132,7 +132,7 @@ class RelationshipsRepository extends BaseRepository { async retrieveParallelRelationships() { const all_relationships = await this.retrieveAll({ versions: 'latest', - lookupRefs: true + lookupRefs: true, }); // Create a mapping of rel_key (source_ref--relationship_type--target_ref) diff --git a/app/services/reports-service.js b/app/services/reports-service.js index af75e6e3..3bdce9c1 100644 --- a/app/services/reports-service.js +++ b/app/services/reports-service.js @@ -45,7 +45,7 @@ class ReportsService { * target_ref, and relationship_type. * @returns {Promise} Map of relationship keys to arrays of parallel relationships */ - async getParallelRelationships(options = {lookupRefs: true}) { + async getParallelRelationships(options = { lookupRefs: true }) { const relationshipMap = await relationshipsRepository.retrieveParallelRelationships(); // Add identity information to each relationship in the map From 5bdea1be82eaa812b622a611e3ab03570ab796af Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 18 Feb 2026 12:38:06 -0500 Subject: [PATCH 207/370] chore: clean imports --- app/services/system/validate-service.js | 45 +------------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index abe91d5b..88ded93c 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -1,48 +1,5 @@ 'use strict'; - -const { - tacticSchema, - techniqueSchema, - techniquePartialSchema, - groupSchema, - malwareSchema, - toolSchema, - mitigationSchema, - assetSchema, - dataSourceSchema, - campaignSchema, - dataComponentSchema, - detectionStrategySchema, - analyticSchema, - matrixSchema, - relationshipSchema, - collectionSchema, - markingDefinitionSchema, - relationshipPartialSchema, - campaignPartialSchema, - groupPartialSchema, - malwarePartialSchema, - toolPartialSchema, -} = require('@mitre-attack/attack-data-model/dist'); - -const STIX_SCHEMAS = { - 'x-mitre-tactic': tacticSchema, - 'attack-pattern': { partial: techniquePartialSchema, full: techniqueSchema }, - 'intrusion-set': { partial: groupPartialSchema, full: groupSchema }, - malware: { partial: malwarePartialSchema, full: malwareSchema }, - tool: { partial: toolPartialSchema, full: toolSchema }, - campaign: { partial: campaignPartialSchema, full: campaignSchema }, - relationship: { partial: relationshipPartialSchema, full: relationshipSchema }, - 'course-of-action': mitigationSchema, - 'marking-definition': markingDefinitionSchema, - 'x-mitre-asset': assetSchema, - 'x-mitre-data-source': dataSourceSchema, - 'x-mitre-data-component': dataComponentSchema, - 'x-mitre-detection-strategy': detectionStrategySchema, - 'x-mitre-analytic': analyticSchema, - 'x-mitre-matrix': matrixSchema, - 'x-mitre-collection': collectionSchema, -}; +const { STIX_SCHEMAS } = require('../../lib/validation-schemas'); /** * Configuration for transforming validation errors (to warnings or suppression) From abc66697549374cdcaa5c80e1bbe234a0119aabb Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 18 Feb 2026 14:14:00 -0500 Subject: [PATCH 208/370] chore: code cleanup --- app/services/system/validate-service.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index 88ded93c..d957f7d9 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -76,8 +76,8 @@ function getSchema(type, status) { return status === 'work-in-progress' ? entry.partial : entry.full; } else { return status === 'work-in-progress' ? entry.partial() : entry; + } } -} /** * Check if a validation error should be transformed (converted to warning or suppressed) @@ -208,7 +208,6 @@ exports.validateStixObject = function (payload) { // Validate STIX data const stixResult = stixSchema.safeParse(stix); - // const stixResult = baseSchema.safeParse(stix); if (stixResult.success) { return { From d9bcafddcd0d1aae73dd59223548ae7c7159fd25 Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 18 Feb 2026 14:14:48 -0500 Subject: [PATCH 209/370] chore: fox identation --- app/services/system/validate-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index d957f7d9..29aa2574 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -76,8 +76,8 @@ function getSchema(type, status) { return status === 'work-in-progress' ? entry.partial : entry.full; } else { return status === 'work-in-progress' ? entry.partial() : entry; - } } +} /** * Check if a validation error should be transformed (converted to warning or suppressed) From 7a8208fffb682ee67564a497981f5975abd23ccd Mon Sep 17 00:00:00 2001 From: adpare Date: Wed, 18 Feb 2026 14:42:19 -0500 Subject: [PATCH 210/370] chore: code cleanup --- app/lib/validation-middleware.js | 70 +----------------------- app/services/system/validate-service.js | 72 ++++++++++++++++++++----- 2 files changed, 61 insertions(+), 81 deletions(-) diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js index 240347a9..775460df 100644 --- a/app/lib/validation-middleware.js +++ b/app/lib/validation-middleware.js @@ -4,7 +4,7 @@ const { z, ZodError } = require('zod'); const { StatusCodes } = require('http-status-codes'); const logger = require('../lib/logger'); const { processValidationIssues } = require('../services/system/validate-service'); -const { STIX_SCHEMAS } = require('../lib/validation-schemas'); +const { getSchema } = require('../services/system/validate-service'); const { createAttackIdSchema, stixTypeToAttackIdMapping, @@ -159,74 +159,6 @@ function middleware(stixSchema, options = {}) { }; } -/** - * Get the schema to use for validating a STIX object. - * - * Some STIX types define both a "base" schema and "checks" (refinements), - * while others only define a single schema (no refinements). This helper - * composes the correct schema based on the STIX type and workflow status. - * - * Composition order (for schemas with checks): - * base → .omit() → .partial() (if WIP) → .check(checks) - * - * This ordering is critical because Zod v4.3.6+ disallows .omit(), .pick(), - * and .partial() on schemas that already have .check() applied. - * - * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") - * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") - * @param {string[]} omitStixFields - Array of STIX field names to omit from validation - * @returns {Object|null} Zod schema, or null if the STIX type is unknown - */ -function getSchema( - stixType, - status, - omitStixFields = ['x_mitre_attack_spec_version', 'external_references'], -) { - const admSchemaRef = STIX_SCHEMAS[stixType]; - if (!admSchemaRef) return null; - - const isPartial = status === 'work-in-progress'; - let stixSchema; - - if (admSchemaRef.base && admSchemaRef.checks) { - // Schema with refinements: compose in the safe order (omit/partial BEFORE check) - stixSchema = admSchemaRef.base; - - if (omitStixFields.length > 0) { - const omitObject = omitStixFields.reduce((acc, field) => { - acc[field] = true; - return acc; - }, {}); - stixSchema = stixSchema.omit(omitObject); - } - - if (isPartial) { - stixSchema = stixSchema.partial(); - } - - // Re-apply refinements last - stixSchema = stixSchema.check(admSchemaRef.checks); - } else { - // Simple schema (no refinements): safe to call .omit() and .partial() directly - stixSchema = admSchemaRef; - - if (omitStixFields.length > 0) { - const omitObject = omitStixFields.reduce((acc, field) => { - acc[field] = true; - return acc; - }, {}); - stixSchema = stixSchema.omit(omitObject); - } - - if (isPartial) { - stixSchema = stixSchema.partial(); - } - } - - logger.debug('Resolved STIX schema:', { stixType, status, isPartial, omitStixFields }); - return stixSchema; -} - /** * Pre-configured validation middleware factory that uses runtime configuration. * The middleware reads the config value at request time to support dynamic config changes (e.g., during tests). diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index 29aa2574..97eaae4d 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -1,5 +1,6 @@ 'use strict'; const { STIX_SCHEMAS } = require('../../lib/validation-schemas'); +const logger = require('../../lib/logger'); /** * Configuration for transforming validation errors (to warnings or suppression) @@ -60,24 +61,71 @@ exports.ERROR_TRANSFORMATION_RULES = ERROR_TRANSFORMATION_RULES; /** * Get the schema to use for validating a STIX object. * - * Some STIX types define both a "partial" (work-in-progress) and "full" schema, - * while others only define a single schema. This helper resolves the correct - * schema based on the STIX type and object status. + * Some STIX types define both a "base" schema and "checks" (refinements), + * while others only define a single schema (no refinements). This helper + * composes the correct schema based on the STIX type and workflow status. * - * @param {string} type - The STIX `type` being validated (e.g. "attack-pattern") - * @param {string} status - The object status (e.g. "work-in-progress", "awaiting-review", "reviewed") - * @returns {Object} Zod schema + * Composition order (for schemas with checks): + * base → .omit() → .partial() (if WIP) → .check(checks) + * + * This ordering is critical because Zod v4.3.6+ disallows .omit(), .pick(), + * and .partial() on schemas that already have .check() applied. + * + * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") + * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") + * @param {string[]} omitStixFields - Array of STIX field names to omit from validation + * @returns {Object|null} Zod schema, or null if the STIX type is unknown */ -function getSchema(type, status) { - const entry = STIX_SCHEMAS[type]; - if (!entry) return null; +function getSchema( + stixType, + status, + omitStixFields = ['x_mitre_attack_spec_version', 'external_references'], +) { + const admSchemaRef = STIX_SCHEMAS[stixType]; + if (!admSchemaRef) return null; + + const isPartial = status === 'work-in-progress'; + let stixSchema; + + if (admSchemaRef.base && admSchemaRef.checks) { + // Schema with refinements: compose in the safe order (omit/partial BEFORE check) + stixSchema = admSchemaRef.base; - if (entry.partial && entry.full) { - return status === 'work-in-progress' ? entry.partial : entry.full; + if (omitStixFields.length > 0) { + const omitObject = omitStixFields.reduce((acc, field) => { + acc[field] = true; + return acc; + }, {}); + stixSchema = stixSchema.omit(omitObject); + } + + if (isPartial) { + stixSchema = stixSchema.partial(); + } + + // Re-apply refinements last + stixSchema = stixSchema.check(admSchemaRef.checks); } else { - return status === 'work-in-progress' ? entry.partial() : entry; + // Simple schema (no refinements): safe to call .omit() and .partial() directly + stixSchema = admSchemaRef; + + if (omitStixFields.length > 0) { + const omitObject = omitStixFields.reduce((acc, field) => { + acc[field] = true; + return acc; + }, {}); + stixSchema = stixSchema.omit(omitObject); + } + + if (isPartial) { + stixSchema = stixSchema.partial(); + } } + + logger.debug('Resolved STIX schema:', { stixType, status, isPartial, omitStixFields }); + return stixSchema; } +exports.getSchema = getSchema; /** * Check if a validation error should be transformed (converted to warning or suppressed) From 2e7fda8b0b2b15707871d22deda81bd4933d2ddb Mon Sep 17 00:00:00 2001 From: Jared Ondricek Date: Fri, 20 Feb 2026 11:03:30 -0600 Subject: [PATCH 211/370] ci: add environment variable for MongoDB store crypto secret in regression tests --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fecf9d1c..3e206c29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,8 @@ jobs: - name: Run regression tests with code coverage run: npm run coverage:cobertura + env: + MONGOSTORE_CRYPTO_SECRET: 'ThisisASecretKeyForTestingPurposesOnly1234567890!@#$' - name: Upload Coverage to CodeCov uses: codecov/codecov-action@v4 From 441613e9ba46eb3f1fc15829c402b05711b99e8c Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 20 Feb 2026 16:51:59 -0500 Subject: [PATCH 212/370] fix: update function to generate secrets that satisfy complexity requirements --- app/config/config.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/app/config/config.js b/app/config/config.js index efeabfee..dc8858ef 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -10,13 +10,30 @@ const packageJson = require('../../package.json'); // - Restarting the server will force the users to login again // - Sessions cannot be shared across server instances // Setting the SESSION_SECRET environment variable will override this generated value -function generateSecret() { - const stringBase = 'base64'; - const byteLength = 48; - const buffer = crypto.randomBytes(byteLength); - const secret = buffer.toString(stringBase); +function generateSecret(length = 48) { + const SPECIAL = "!@#$%^&*()_+-=[]{};':\"\\|,.<>/?"; + if (length < 8) throw new Error("length must be >= 8"); - return secret; + // run until we have a secret that matches the complexity requirements + while (true) { + // remove the last two characters to ensure we can add the special characters + const base = crypto.randomBytes(length).toString("base64").slice(0, length - 2); + + const s1 = SPECIAL[crypto.randomInt(SPECIAL.length)]; + const s2 = SPECIAL[crypto.randomInt(SPECIAL.length)]; + + const secret = base + s1 + s2; + + if ( + secret.length >= 8 && + (secret.match(/[A-Z]/g) || []).length >= 2 && + (secret.match(/[a-z]/g) || []).length >= 2 && + (secret.match(/[0-9]/g) || []).length >= 2 && + (secret.match(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/g) || []).length >= 2 + ) { + return secret; + } + } } const defaultSessionSecret = generateSecret(); From b53beab479c14e2f85540fd6d2f27cb4eb736fbf Mon Sep 17 00:00:00 2001 From: adpare Date: Fri, 20 Feb 2026 16:52:41 -0500 Subject: [PATCH 213/370] chore: fix indentation --- app/config/config.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/config/config.js b/app/config/config.js index dc8858ef..ba84ff20 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -11,13 +11,16 @@ const packageJson = require('../../package.json'); // - Sessions cannot be shared across server instances // Setting the SESSION_SECRET environment variable will override this generated value function generateSecret(length = 48) { - const SPECIAL = "!@#$%^&*()_+-=[]{};':\"\\|,.<>/?"; - if (length < 8) throw new Error("length must be >= 8"); + const SPECIAL = '!@#$%^&*()_+-=[]{};\':"\\|,.<>/?'; + if (length < 8) throw new Error('length must be >= 8'); // run until we have a secret that matches the complexity requirements while (true) { // remove the last two characters to ensure we can add the special characters - const base = crypto.randomBytes(length).toString("base64").slice(0, length - 2); + const base = crypto + .randomBytes(length) + .toString('base64') + .slice(0, length - 2); const s1 = SPECIAL[crypto.randomInt(SPECIAL.length)]; const s2 = SPECIAL[crypto.randomInt(SPECIAL.length)]; @@ -29,7 +32,7 @@ function generateSecret(length = 48) { (secret.match(/[A-Z]/g) || []).length >= 2 && (secret.match(/[a-z]/g) || []).length >= 2 && (secret.match(/[0-9]/g) || []).length >= 2 && - (secret.match(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/g) || []).length >= 2 + (secret.match(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/g) || []).length >= 2 ) { return secret; } From 7fe854e8b320498795a490cc6dd4cf63ee3079e4 Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 23 Feb 2026 16:08:53 -0500 Subject: [PATCH 214/370] fix: update connect-mongo version to use cryptoAdapter for encryption --- app/config/config.js | 31 +- app/index.js | 7 +- package-lock.json | 4184 ++++++++++++++---------------------------- package.json | 2 +- 4 files changed, 1341 insertions(+), 2883 deletions(-) diff --git a/app/config/config.js b/app/config/config.js index ba84ff20..a4b20726 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -10,33 +10,14 @@ const packageJson = require('../../package.json'); // - Restarting the server will force the users to login again // - Sessions cannot be shared across server instances // Setting the SESSION_SECRET environment variable will override this generated value -function generateSecret(length = 48) { - const SPECIAL = '!@#$%^&*()_+-=[]{};\':"\\|,.<>/?'; - if (length < 8) throw new Error('length must be >= 8'); - // run until we have a secret that matches the complexity requirements - while (true) { - // remove the last two characters to ensure we can add the special characters - const base = crypto - .randomBytes(length) - .toString('base64') - .slice(0, length - 2); +function generateSecret() { + const stringBase = 'base64'; + const byteLength = 48; + const buffer = crypto.randomBytes(byteLength); + const secret = buffer.toString(stringBase); - const s1 = SPECIAL[crypto.randomInt(SPECIAL.length)]; - const s2 = SPECIAL[crypto.randomInt(SPECIAL.length)]; - - const secret = base + s1 + s2; - - if ( - secret.length >= 8 && - (secret.match(/[A-Z]/g) || []).length >= 2 && - (secret.match(/[a-z]/g) || []).length >= 2 && - (secret.match(/[0-9]/g) || []).length >= 2 && - (secret.match(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/g) || []).length >= 2 - ) { - return secret; - } - } + return secret; } const defaultSessionSecret = generateSecret(); diff --git a/app/index.js b/app/index.js index 6537e4d9..e4322413 100644 --- a/app/index.js +++ b/app/index.js @@ -131,6 +131,7 @@ exports.initializeApp = async function () { // Configure server-side sessions const session = require('express-session'); const MongoStore = require('connect-mongo'); + const { createWebCryptoAdapter } = require("connect-mongo"); const mongoose = require('mongoose'); // Generate unique session cookie name based on container hostname @@ -149,11 +150,11 @@ exports.initializeApp = async function () { secret: config.session.secret, resave: false, saveUninitialized: false, - store: MongoStore.create({ + store: MongoStore.MongoStore.create({ client: mongoose.connection.getClient(), - crypto: { + cryptoAdapter: createWebCryptoAdapter({ secret: config.session.mongoStoreCryptoSecret, - }, + }), }), }; app.use(session(sessionOptions)); diff --git a/package-lock.json b/package-lock.json index 3d78a457..98ec9194 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", "compression": "^1.8.1", - "connect-mongo": "^4.6.0", + "connect-mongo": "6.0.0", "convict": "^6.2.4", "cors": "^2.8.5", "express": "^4.21.2", @@ -125,2956 +125,1533 @@ "url": "https://github.com/sponsors/philsturgeon" } }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=18" } }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@codedependant/semantic-release-docker": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@codedependant/semantic-release-docker/-/semantic-release-docker-5.1.1.tgz", + "integrity": "sha512-gACzBuAVRBRAJOvGqCXZoMGSpVlyqbAp/LoiJnBc/Fl4B+ZqJyI95uwRNJXSuMQXTUI/I49RTmdetWWU8f/a5w==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@actions/core": "^1.11.1", + "@semantic-release/error": "^3.0.0", + "debug": "^4.1.1", + "execa": "^4.0.2", + "handlebars": "^4.7.7", + "object-hash": "^3.0.0", + "semver": "^7.3.2" } }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "license": "Apache-2.0", + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "engines": { + "node": ">=0.1.90" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", + "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@commitlint/format": "^19.8.1", + "@commitlint/lint": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/read": "^19.8.1", + "@commitlint/types": "^19.8.1", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=v18" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/config-conventional": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", + "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "@commitlint/types": "^19.8.1", + "conventional-changelog-conventionalcommits": "^7.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=v18" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.970.0.tgz", - "integrity": "sha512-1JTW7UFTMjv0U61bCMWnqLF3fIUcCAfrhZX33XcKMs1CchhdDTTn/IBfPJPD7RanyfjeuP7sOtEHNYqPqLFeJQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/ensure": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", + "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-node": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.970.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@commitlint/types": "^19.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.970.0.tgz", - "integrity": "sha512-ArmgnOsSCXN5VyIvZb4kSP5hpqlRRHolrMtKQ/0N8Hw4MTb7/IeYHSZzVPNzzkuX6gn5Aj8txoUnDPM8O7pc9g==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.970.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@commitlint/execute-rule": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", + "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/core": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.970.0.tgz", - "integrity": "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/format": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", + "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws-sdk/xml-builder": "3.969.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.970.0.tgz", - "integrity": "sha512-mZfK/fnmfHkbz1TktCnNKMxNdmbbBoa+Ywx9iKxO0dMHp1EMnuF+z31BIH5EEp2iYVW+R71lh97o1FtGTcATgw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/is-ignored": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", + "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@commitlint/types": "^19.8.1", + "semver": "^7.6.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.970.0.tgz", - "integrity": "sha512-rtVzXzEtAfZBfh+lq3DAvRar4c3jyptweOAJR2DweyXx71QSMY+O879hjpMwES7jl07a3O1zlnFIDo4KP/96kQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/lint": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", + "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@commitlint/is-ignored": "^19.8.1", + "@commitlint/parse": "^19.8.1", + "@commitlint/rules": "^19.8.1", + "@commitlint/types": "^19.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.970.0.tgz", - "integrity": "sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/load": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", + "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" + "@commitlint/config-validator": "^19.8.1", + "@commitlint/execute-rule": "^19.8.1", + "@commitlint/resolve-extends": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.970.0.tgz", - "integrity": "sha512-L5R1hN1FY/xCmH65DOYMXl8zqCFiAq0bAq8tJZU32mGjIl1GzGeOkeDa9c461d81o7gsQeYzXyqFD3vXEbJ+kQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-login": "3.970.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.970.0", - "@aws-sdk/credential-provider-web-identity": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@commitlint/message": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.970.0.tgz", - "integrity": "sha512-C+1dcLr+p2E+9hbHyvrQTZ46Kj4vC2RoP6N935GEukHQa637ZjXs8VlyHJ2xTvbvwwLZQNiu56Cx7o/OFOqw1A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@commitlint/types": "^19.8.1", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.970.0.tgz", - "integrity": "sha512-nMM0eeVuiLtw1taLRQ+H/H5Qp11rva8ILrzAQXSvlbDeVmbc7d8EeW5Q2xnCJu+3U+2JNZ1uxqIL22pB2sLEMA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/read": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", + "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-ini": "3.970.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.970.0", - "@aws-sdk/credential-provider-web-identity": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@commitlint/top-level": "^19.8.1", + "@commitlint/types": "^19.8.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.970.0.tgz", - "integrity": "sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", + "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@commitlint/config-validator": "^19.8.1", + "@commitlint/types": "^19.8.1", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.970.0.tgz", - "integrity": "sha512-ROb+Aijw8nzkB14Nh2XRH861++SeTZykUzk427y8YtgTLxjAOjgDTchDUFW2Fx6GFWkSjqJ3sY7SZyb33IqyFw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/rules": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", + "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-sso": "3.970.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/token-providers": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@commitlint/ensure": "^19.8.1", + "@commitlint/message": "^19.8.1", + "@commitlint/to-lines": "^19.8.1", + "@commitlint/types": "^19.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.970.0.tgz", - "integrity": "sha512-r7tnYJJg+B6QvnsRHSW5vDol+ks6n+5jBZdCFdGyK63hjcMRMqHx59zEH8O47UR1PFv5hS2Q3uGz6HXvVtP40Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@commitlint/to-lines": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", + "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.970.0.tgz", - "integrity": "sha512-vBQJLwr1VSUD8jWgaS0nuWIGWXkUlfv+c/fXfYvgMWPFow9ShggGo/lfo/y4OC69mbWfMyScIxBVUp78/st9tA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/top-level": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", + "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.970.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/credential-provider-cognito-identity": "3.970.0", - "@aws-sdk/credential-provider-env": "3.970.0", - "@aws-sdk/credential-provider-http": "3.970.0", - "@aws-sdk/credential-provider-ini": "3.970.0", - "@aws-sdk/credential-provider-login": "3.970.0", - "@aws-sdk/credential-provider-node": "3.970.0", - "@aws-sdk/credential-provider-process": "3.970.0", - "@aws-sdk/credential-provider-sso": "3.970.0", - "@aws-sdk/credential-provider-web-identity": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "find-up": "^7.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz", - "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=v18" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz", - "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz", - "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.969.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=20.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.970.0.tgz", - "integrity": "sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@smithy/core": "^3.20.6", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=20.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.970.0.tgz", - "integrity": "sha512-RIl8s4DCa31MXtRFw23iU90OqEoWuwQxiZOZshzsPtjyrunhHFjyZJEqb+vuQcYd1o22SMaYa3lPJRp64OH35Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.970.0", - "@aws-sdk/middleware-host-header": "3.969.0", - "@aws-sdk/middleware-logger": "3.969.0", - "@aws-sdk/middleware-recursion-detection": "3.969.0", - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/region-config-resolver": "3.969.0", - "@aws-sdk/types": "3.969.0", - "@aws-sdk/util-endpoints": "3.970.0", - "@aws-sdk/util-user-agent-browser": "3.969.0", - "@aws-sdk/util-user-agent-node": "3.970.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.20.6", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-retry": "^4.4.23", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.22", - "@smithy/util-defaults-mode-node": "^4.2.25", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz", - "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==", + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.970.0.tgz", - "integrity": "sha512-YO8KgJecxHIFMhfoP880q51VXFL9V1ELywK5yzVEqzyrwqoG93IUmnTygBUylQrfkbH+QqS0FxEdgwpP3fcwoQ==", + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, "dependencies": { - "@aws-sdk/core": "3.970.0", - "@aws-sdk/nested-clients": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@eslint/core": "^0.17.0" }, "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz", - "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==", + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.970.0.tgz", - "integrity": "sha512-TZNZqFcMUtjvhZoZRtpEGQAdULYiy6rcGiXAbLU7e9LSpIYlRqpLa207oMNfgbzlL2PnHko+eVg8rajDiSOYCg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.2.tgz", - "integrity": "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz", - "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.969.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.970.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.970.0.tgz", - "integrity": "sha512-TNQpwIVD6SxMwkD+QKnaujKVyXy5ljN3O3jrI7nCHJ3GlJu5xJrd8yuBnanYCcrn3e2zwdfOh4d4zJAZvvIvVw==", + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, "dependencies": { - "@aws-sdk/middleware-user-agent": "3.970.0", - "@aws-sdk/types": "3.969.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.969.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz", - "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@ewoudenberg/difflib": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ewoudenberg/difflib/-/difflib-0.1.0.tgz", + "integrity": "sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==", + "dev": true, "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, + "heap": ">= 0.2.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=14" } }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, "license": "Apache-2.0", - "optional": true, - "peer": true, "engines": { - "node": ">=18.0.0" + "node": ">=18.18.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.18.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@codedependant/semantic-release-docker": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@codedependant/semantic-release-docker/-/semantic-release-docker-5.1.1.tgz", - "integrity": "sha512-gACzBuAVRBRAJOvGqCXZoMGSpVlyqbAp/LoiJnBc/Fl4B+ZqJyI95uwRNJXSuMQXTUI/I49RTmdetWWU8f/a5w==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@actions/core": "^1.11.1", - "@semantic-release/error": "^3.0.0", - "debug": "^4.1.1", - "execa": "^4.0.2", - "handlebars": "^4.7.7", - "object-hash": "^3.0.0", - "semver": "^7.3.2" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "license": "MIT", - "optional": true, + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=0.1.90" + "node": ">=12" } }, - "node_modules/@commitlint/cli": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", - "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^19.8.1", - "@commitlint/lint": "^19.8.1", - "@commitlint/load": "^19.8.1", - "@commitlint/read": "^19.8.1", - "@commitlint/types": "^19.8.1", - "tinyexec": "^1.0.0", - "yargs": "^17.0.0" - }, - "bin": { - "commitlint": "cli.js" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=v18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@commitlint/config-conventional": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", - "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", - "dependencies": { - "@commitlint/types": "^19.8.1", - "conventional-changelog-conventionalcommits": "^7.0.2" - }, "engines": { - "node": ">=v18" + "node": ">=8" } }, - "node_modules/@commitlint/config-validator": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", - "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^19.8.1", - "ajv": "^8.11.0" + "jest-get-type": "^29.6.3" }, "engines": { - "node": ">=v18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@commitlint/ensure": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", - "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^19.8.1", - "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1", - "lodash.snakecase": "^4.1.1", - "lodash.startcase": "^4.4.0", - "lodash.upperfirst": "^4.3.1" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=v18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@commitlint/execute-rule": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", - "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, "engines": { - "node": ">=v18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@commitlint/format": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", - "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^19.8.1", - "chalk": "^5.3.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=v18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@commitlint/is-ignored": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", - "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^19.8.1", - "semver": "^7.6.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=v18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@commitlint/lint": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", - "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^19.8.1", - "@commitlint/parse": "^19.8.1", - "@commitlint/rules": "^19.8.1", - "@commitlint/types": "^19.8.1" + "color-name": "~1.1.4" }, "engines": { - "node": ">=v18" + "node": ">=7.0.0" } }, - "node_modules/@commitlint/load": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", - "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^19.8.1", - "@commitlint/execute-rule": "^19.8.1", - "@commitlint/resolve-extends": "^19.8.1", - "@commitlint/types": "^19.8.1", - "chalk": "^5.3.0", - "cosmiconfig": "^9.0.0", - "cosmiconfig-typescript-loader": "^6.1.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", - "lodash.uniq": "^4.5.0" - }, - "engines": { - "node": ">=v18" - } + "license": "MIT" }, - "node_modules/@commitlint/message": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", - "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { - "node": ">=v18" + "node": ">=6.0.0" } }, - "node_modules/@commitlint/parse": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", - "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^19.8.1", - "conventional-changelog-angular": "^7.0.0", - "conventional-commits-parser": "^5.0.0" - }, - "engines": { - "node": ">=v18" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@commitlint/read": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", - "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", - "dev": true, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@mitre-attack/attack-data-model": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.10.0.tgz", + "integrity": "sha512-whLa1X8GURoNvKgpbAqZe2EymL9I5/ncbdRSDKgfYeYkt0iu3FwfzKVIlcq2sn5gKZ68o94kxSM5TDsi+2r6PA==", + "license": "APACHE-2.0", + "dependencies": { + "axios": "^1.9.0", + "uuid": "^10.0.0", + "zod": "^4.3.6" + } + }, + "node_modules/@mitre-attack/attack-data-model/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@mitre-attack/attack-data-model/node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", "license": "MIT", "dependencies": { - "@commitlint/top-level": "^19.8.1", - "@commitlint/types": "^19.8.1", - "git-raw-commits": "^4.0.0", - "minimist": "^1.2.8", - "tinyexec": "^1.0.0" - }, + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", "engines": { - "node": ">=v18" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@commitlint/resolve-extends": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", - "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", - "dependencies": { - "@commitlint/config-validator": "^19.8.1", - "@commitlint/types": "^19.8.1", - "global-directory": "^4.0.1", - "import-meta-resolve": "^4.0.0", - "lodash.mergewith": "^4.6.2", - "resolve-from": "^5.0.0" - }, "engines": { - "node": ">=v18" + "node": ">= 20" } }, - "node_modules/@commitlint/rules": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", - "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^19.8.1", - "@commitlint/message": "^19.8.1", - "@commitlint/to-lines": "^19.8.1", - "@commitlint/types": "^19.8.1" + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">=v18" + "node": ">= 20" } }, - "node_modules/@commitlint/to-lines": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", - "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "node_modules/@octokit/endpoint": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, "engines": { - "node": ">=v18" + "node": ">= 20" } }, - "node_modules/@commitlint/top-level": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", - "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^7.0.0" + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">=v18" + "node": ">= 20" } }, - "node_modules/@commitlint/types": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", - "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", + "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", "dev": true, "license": "MIT", "dependencies": { - "@types/conventional-commits-parser": "^5.0.0", - "chalk": "^5.3.0" + "@octokit/types": "^15.0.1" }, "engines": { - "node": ">=v18" + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", - "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", + "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", + "dev": true, "license": "MIT", "dependencies": { - "@so-ric/colorspace": "^1.1.6", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@octokit/openapi-types": "^26.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "node_modules/@octokit/plugin-retry": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 20" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@octokit/core": ">=7" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@octokit/plugin-throttling": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 20" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@octokit/core": "^7.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@octokit/request": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz", + "integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "json-with-bigint": "^3.5.3", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 20" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 20" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@octokit/openapi-types": "^27.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "@noble/hashes": "^1.1.5" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "optional": true, + "engines": { + "node": ">=14" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12.22.0" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" + "graceful-fs": "4.2.10" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12.22.0" } }, - "node_modules/@ewoudenberg/difflib": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@ewoudenberg/difflib/-/difflib-0.1.0.tgz", - "integrity": "sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==", + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true, - "dependencies": { - "heap": ">= 0.2.0" - } + "license": "ISC" }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@pnpm/npm-conf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", "dev": true, "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, "engines": { - "node": ">=14" + "node": ">=12" } }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, "engines": { - "node": ">=18.18.0" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=18.18.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" + "license": "MIT", + "dependencies": { + "meow": "^13.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@semantic-release/commit-analyzer/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=18.18" + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.17" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@semantic-release/github": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", + "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", + "url-join": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=20.8.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "semantic-release": ">=24.1.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/@semantic-release/npm": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", + "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.9.3", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "license": "MIT" - }, - "node_modules/@mitre-attack/attack-data-model": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.5.3.tgz", - "integrity": "sha512-XYtV7dFosqZNMs04VG+X1xVktjI0blZKrpXfyKXMb8bum0dSucRegTGHBZDYqDQLNbFdzEfSh9IH92QT8SiI/Q==", - "license": "APACHE-2.0", - "dependencies": { - "axios": "^1.9.0", - "uuid": "^10.0.0", - "zod": "^4.0.5" - } - }, - "node_modules/@mitre-attack/attack-data-model/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz", - "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==", - "license": "MIT", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/core": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", - "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.3", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/endpoint": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", - "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/graphql": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", - "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", - "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.1.tgz", - "integrity": "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^15.0.1" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", - "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.2.tgz", - "integrity": "sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^26.0.0" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", - "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": ">=7" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", - "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@octokit/core": "^7.0.0" - } - }, - "node_modules/@octokit/request": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", - "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^11.0.2", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", - "fast-content-type-parse": "^3.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/request-error": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", - "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^16.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/types": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", - "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^27.0.0" - } - }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", - "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@semantic-release/commit-analyzer": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", - "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "import-from-esm": "^2.0.0", - "lodash-es": "^4.17.21", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", - "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", - "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/commit-analyzer/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", - "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@semantic-release/github": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.6.tgz", - "integrity": "sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/core": "^7.0.0", - "@octokit/plugin-paginate-rest": "^13.0.0", - "@octokit/plugin-retry": "^8.0.0", - "@octokit/plugin-throttling": "^11.0.0", - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "debug": "^4.3.4", - "dir-glob": "^3.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "issue-parser": "^7.0.0", - "lodash-es": "^4.17.21", - "mime": "^4.0.0", - "p-filter": "^4.0.0", - "tinyglobby": "^0.2.14", - "url-join": "^5.0.0" - }, - "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=24.1.0" - } - }, - "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/npm": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", - "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^4.0.0", - "aggregate-error": "^5.0.0", - "execa": "^9.0.0", - "fs-extra": "^11.0.0", - "lodash-es": "^4.17.21", - "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", - "npm": "^10.9.3", - "rc": "^1.2.8", - "read-pkg": "^9.0.0", - "registry-auth-token": "^5.0.0", - "semver": "^7.1.2", - "tempy": "^3.0.0" - }, - "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", - "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/npm/node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@semantic-release/npm/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@semantic-release/npm/node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/release-notes-generator": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", - "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0", - "debug": "^4.0.0", - "get-stream": "^7.0.0", - "import-from-esm": "^2.0.0", - "into-stream": "^7.0.0", - "lodash-es": "^4.17.21", - "read-package-up": "^11.0.0" - }, - "engines": { - "node": ">=20.8.1" - }, - "peerDependencies": { - "semantic-release": ">=20.1.0" - } - }, - "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", - "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", - "dev": true, - "license": "ISC", - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", - "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", - "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", - "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.20.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.6.tgz", - "integrity": "sha512-BpAffW1mIyRZongoKBbh3RgHG+JDHJek/8hjA/9LnPunM+ejorO6axkxCgwxCe4K//g/JdPeR9vROHDYr/hfnQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/middleware-serde": "^4.2.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", - "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", - "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", - "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", - "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", - "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.7.tgz", - "integrity": "sha512-SCmhUG1UwtnEhF5Sxd8qk7bJwkj1BpFzFlHkXqKCEmDPLrRjJyTGM0EhqT7XBtDaDJjCfjRJQodgZcKDR843qg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/core": "^3.20.6", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.23", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.23.tgz", - "integrity": "sha512-lLEmkQj7I7oKfvZ1wsnToGJouLOtfkMXDKRA1Hi6F+mMp5O1N8GcVWmVeNgTtgZtd0OTXDTI2vpVQmeutydGew==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", - "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", - "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", - "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", - "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", - "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", - "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", - "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", - "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", - "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", - "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", - "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "4.10.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.8.tgz", - "integrity": "sha512-wcr3UEL26k7lLoyf9eVDZoD1nNY3Fa1gbNuOXvfxvVWLGkOVW+RYZgUUp/bXHryJfycIOQnBq9o1JAE00ax8HQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/core": "^3.20.6", - "@smithy/middleware-endpoint": "^4.4.7", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", - "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", - "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/querystring-parser": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" - }, + "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@semantic-release/release-notes-generator": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.0.tgz", + "integrity": "sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^2.0.0", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.22", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.22.tgz", - "integrity": "sha512-O2WXr6ZRqPnbyoepb7pKcLt1QL6uRfFzGYJ9sGb5hMJQi7v/4RjRmCQa9mNjA0YiXqsc5lBmLXqJPhjM1Vjv5A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", + "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", + "dev": true, + "license": "ISC", "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.25.tgz", - "integrity": "sha512-7uMhppVNRbgNIpyUBVRfjGHxygP85wpXalRvn9DvUlCx4qgy1AB/uxOPSiDx/jFyrwD3/BypQhx1JK7f3yxrAw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", + "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/config-resolver": "^4.4.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", - "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", - "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@smithy/util-retry": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", - "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@smithy/service-error-classification": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/util-stream": { - "version": "4.5.10", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", - "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "type-detect": "4.0.8" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, + "node_modules/@sinonjs/samsam": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", + "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" } }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=4" } }, "node_modules/@so-ric/colorspace": { @@ -3202,21 +1779,21 @@ "license": "MIT" }, "node_modules/@types/multer": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", - "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/normalize-package-data": { @@ -3277,13 +1854,12 @@ "license": "MIT" }, "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", "license": "MIT", "peer": true, "dependencies": { - "@types/node": "*", "@types/webidl-conversions": "*" } }, @@ -3327,9 +1903,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -3377,9 +1953,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3424,9 +2000,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { @@ -3557,20 +2133,20 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -3604,27 +2180,6 @@ } } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -3651,9 +2206,9 @@ "license": "Apache-2.0" }, "node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/body-parser": { @@ -3687,14 +2242,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3726,42 +2273,14 @@ "dev": true, "license": "ISC" }, - "node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", + "node_modules/bson": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", + "license": "Apache-2.0", "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "engines": { + "node": ">=20.19.0" } }, "node_modules/buffer-crc32": { @@ -4413,9 +2932,9 @@ } }, "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", "engines": { "node": ">=20" @@ -4534,19 +3053,20 @@ "license": "ISC" }, "node_modules/connect-mongo": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", - "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-6.0.0.tgz", + "integrity": "sha512-mHxfnTiWk7ZtxmHdcrFBKlr7fCtgGoFpx/oe9jFW0yb2NinagsxEeuol78nUWMpnWyYK0nnuXMlU9wrgUjTE6g==", "license": "MIT", "dependencies": { - "debug": "^4.3.1", - "kruptein": "^3.0.0" + "debug": "^4.4.3", + "kruptein": "3.0.8" }, "engines": { - "node": ">=10" + "node": ">=20.8.0" }, "peerDependencies": { - "mongodb": "^4.1.0" + "express-session": "^1.17.1", + "mongodb": ">=5.0.0" } }, "node_modules/content-disposition": { @@ -4733,9 +3253,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -4743,6 +3263,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cosmiconfig": { @@ -5398,9 +3922,9 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", "dependencies": { @@ -5410,7 +3934,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/js": "9.39.3", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5535,9 +4059,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -5870,13 +4394,13 @@ } }, "node_modules/express-openapi-validator": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.0.tgz", - "integrity": "sha512-gNaMgDb1cAT8QKcuh9WrED9p3mqi/V7yocNrvnE1fOz7e8p8JkbYaTUcOB4VsZKerz/X+Sey7ptTGF5FwsXh8Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.6.2.tgz", + "integrity": "sha512-fkDn4+ImUC4HTJ1g0cek/ItqYhmEO19AglJd2Iw2OJco0jLIbxIlDGVazmXbvvYeziU4Bnah2h+S2tb6NtWg8w==", "license": "MIT", "dependencies": { - "@apidevtools/json-schema-ref-parser": "^14.0.3", - "@types/multer": "^1.4.13", + "@apidevtools/json-schema-ref-parser": "^14.2.1", + "@types/multer": "^2.0.0", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", @@ -5887,8 +4411,8 @@ "media-typer": "^1.1.0", "multer": "^2.0.2", "ono": "^7.1.3", - "path-to-regexp": "^8.2.0", - "qs": "^6.14.0" + "path-to-regexp": "^8.3.0", + "qs": "^6.14.1" }, "peerDependencies": { "express": "*" @@ -5923,22 +4447,26 @@ } }, "node_modules/express-session": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", - "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz", + "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==", "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.7", - "debug": "2.6.9", + "cookie": "~0.7.2", + "cookie-signature": "~1.0.7", + "debug": "~2.6.9", "depd": "~2.0.0", "on-headers": "~1.1.0", "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", + "safe-buffer": "~5.2.1", "uid-safe": "~2.1.5" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-session/node_modules/cookie": { @@ -6034,6 +4562,21 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/express/node_modules/raw-body": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", @@ -6135,26 +4678,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -6670,6 +5193,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -6700,24 +5224,37 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -7017,27 +5554,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", - "peer": true - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7169,16 +5685,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7784,6 +6290,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-with-bigint": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.3.tgz", + "integrity": "sha512-QObKu6nxy7NsxqR0VK4rkXnsNr5L9ElJaGEg+ucJ6J7/suoKZ0n+p76cu9aCqowytxEbwYNzvrMerfMkXneF5A==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -7858,9 +6371,9 @@ } }, "node_modules/jwks-rsa": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.1.tgz", - "integrity": "sha512-r7QdN9TdqI6aFDFZt+GpAqj5yRtMUv23rL2I01i7B8P2/g8F0ioEN6VeSObKgTLs4GmmNJwP9J7Fyp/AYDBGRg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.2.tgz", + "integrity": "sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==", "license": "MIT", "dependencies": { "@types/jsonwebtoken": "^9.0.4", @@ -7912,9 +6425,9 @@ } }, "node_modules/kruptein": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.1.7.tgz", - "integrity": "sha512-Wk3WFZdGtjnTBGlC12SiBsEh51+6lsW7sSRm+MzRVRc3WRPrUQGu4+/n3YQjSPp8x1AJJxPxgLsekFXLiBN2PQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.8.tgz", + "integrity": "sha512-0CyalFA0Cjp3jnziMp0u1uLZW2/ouhQ0mEMfYlroBXNe86na1RwAuwBcdRAegeWZNMfQy/G5fN47g/Axjtqrfw==", "license": "MIT", "dependencies": { "asn1.js": "^5.4.1" @@ -8002,15 +6515,15 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", - "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "dev": true, "license": "MIT" }, @@ -8506,9 +7019,9 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -8528,11 +7041,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -8586,14 +7099,27 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/mocha/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/mocha/node_modules/find-up": { @@ -8630,13 +7156,13 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -8717,33 +7243,64 @@ } }, "node_modules/mongodb": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", - "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.1.0.tgz", + "integrity": "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg==", "license": "Apache-2.0", "peer": true, "dependencies": { - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.6.0", - "socks": "^2.7.1" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.1.1", + "mongodb-connection-string-url": "^7.0.0" }, "engines": { - "node": ">=12.9.0" + "node": ">=20.19.0" }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "@mongodb-js/saslprep": "^1.1.0" + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } } }, "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" } }, "node_modules/mongodb-memory-server": { @@ -8863,37 +7420,10 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/mongodb-memory-server-core/node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/mongoose": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.21.0.tgz", - "integrity": "sha512-dW2U01gN8EVQT5KAO5AkzjbqWc8A/CsEq15jOzq/M9ISpy8jw3iq7W9ZP135h9zykFOMt3AMxq4+anvt2YNJgw==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.0.tgz", + "integrity": "sha512-Bul4Ha6J8IqzFrb0B1xpVzkC3S0sk43dmLSnhFOn8eJlZiLwL5WO6cRymmjaADdCMjUcCpj2ce8hZI6O4ZFSug==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -8986,31 +7516,6 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/mongoose/node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/mongoose/node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/morgan": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", @@ -12746,9 +11251,9 @@ } }, "node_modules/prettier": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.0.tgz", - "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -12859,9 +11364,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -13341,9 +11846,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13726,32 +12231,6 @@ "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "peer": true, - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/sorted-array-functions": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", @@ -13814,9 +12293,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, @@ -14091,20 +12570,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/super-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", @@ -14211,9 +12676,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", - "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "version": "5.31.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.2.tgz", + "integrity": "sha512-uIoesCjDcxnAKj/C/HG5pjHZMQs2K/qmqpUlwLxxaVryGKlgm8Ri+VOza5xywAqf//pgg/hW16RYa6dDuTCOSg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -14273,9 +12738,9 @@ } }, "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14318,50 +12783,63 @@ } }, "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", - "minimatch": "^9.0.4" + "minimatch": "^10.2.2" }, "engines": { "node": ">=18" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -14558,16 +13036,15 @@ } }, "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", - "peer": true, "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/traverse": { @@ -14596,7 +13073,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "devOptional": true, + "dev": true, "license": "0BSD" }, "node_modules/tunnel": { @@ -14736,9 +13213,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { @@ -14906,17 +13383,16 @@ } }, "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", - "peer": true, "dependencies": { - "tr46": "^3.0.0", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/which": { diff --git a/package.json b/package.json index 5f3d533d..17a6a37d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", "compression": "^1.8.1", - "connect-mongo": "^4.6.0", + "connect-mongo": "6.0.0", "convict": "^6.2.4", "cors": "^2.8.5", "express": "^4.21.2", From 43261d4cc786f49e140e66f05062bfee9faf10c1 Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 23 Feb 2026 16:09:30 -0500 Subject: [PATCH 215/370] chore: update import format --- app/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/index.js b/app/index.js index e4322413..e7defbbb 100644 --- a/app/index.js +++ b/app/index.js @@ -131,7 +131,7 @@ exports.initializeApp = async function () { // Configure server-side sessions const session = require('express-session'); const MongoStore = require('connect-mongo'); - const { createWebCryptoAdapter } = require("connect-mongo"); + const { createWebCryptoAdapter } = require('connect-mongo'); const mongoose = require('mongoose'); // Generate unique session cookie name based on container hostname From 7329a228f637584dc88d4a07bbace5471f8212ff Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 23 Feb 2026 16:32:10 -0500 Subject: [PATCH 216/370] chore: add carat to connect-mongo --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98ec9194..f7b1fcc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", "compression": "^1.8.1", - "connect-mongo": "6.0.0", + "connect-mongo": "^6.0.0", "convict": "^6.2.4", "cors": "^2.8.5", "express": "^4.21.2", diff --git a/package.json b/package.json index 17a6a37d..f1288821 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", "compression": "^1.8.1", - "connect-mongo": "6.0.0", + "connect-mongo": "^6.0.0", "convict": "^6.2.4", "cors": "^2.8.5", "express": "^4.21.2", From 797f0c3b18378d684259150b46d9e578c75696fa Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:33:59 -0500 Subject: [PATCH 217/370] feat: consolidate DTO handling using Zod for release tracks --- .../paths/release-tracks-paths.yml | 3 +- app/controllers/release-tracks-controller.js | 11 +- app/lib/release-tracks/export-schemas.js | 179 ++++++++++++++++++ .../release-tracks/release-track-schemas.js | 2 +- app/services/release-tracks/export-service.js | 101 ++-------- .../release-tracks/release-tracks-service.js | 15 +- 6 files changed, 216 insertions(+), 95 deletions(-) create mode 100644 app/lib/release-tracks/export-schemas.js diff --git a/app/api/definitions/paths/release-tracks-paths.yml b/app/api/definitions/paths/release-tracks-paths.yml index 45b448ee..dd67ea65 100644 --- a/app/api/definitions/paths/release-tracks-paths.yml +++ b/app/api/definitions/paths/release-tracks-paths.yml @@ -178,13 +178,14 @@ paths: default: members - name: format in: query - description: 'Output format' + description: 'Output format: snapshot (raw), bundle (STIX 2.1), workbench (with metadata), filesystemstore (not implemented)' schema: type: string enum: - snapshot - bundle - workbench + - filesystemstore default: snapshot responses: '200': diff --git a/app/controllers/release-tracks-controller.js b/app/controllers/release-tracks-controller.js index dd111202..239a9c8d 100644 --- a/app/controllers/release-tracks-controller.js +++ b/app/controllers/release-tracks-controller.js @@ -61,7 +61,7 @@ function parseOptionalQuery(value, schema, defaultValue) { */ function parseSnapshotQueryParams(query) { return { - format: parseOptionalQuery(query.format, formatQuerySchema, 'bundle'), + format: parseOptionalQuery(query.format, formatQuerySchema, 'snapshot'), include: parseOptionalQuery(query.include, includeQuerySchema, undefined), releases: query.releases === 'only' ? 'only' : undefined, version: parseOptionalQuery(query.version, xMitreVersionSchema, undefined), @@ -183,6 +183,15 @@ exports.retrieveLatestSnapshot = async function retrieveLatestSnapshot(req, res, try { const queryOptions = parseSnapshotQueryParams(req.query); + // filesystemstore format is not yet implemented + if (queryOptions.format === 'filesystemstore') { + return next( + new NotImplementedError('release-tracks-controller', 'retrieveLatestSnapshot', { + message: 'The filesystemstore format is not yet implemented', + }), + ); + } + const result = await releaseTracksService.getLatestSnapshot(req.params.id, queryOptions); logger.debug(`Success: Retrieved latest snapshot for track ${req.params.id}`); return res.status(200).send(result); diff --git a/app/lib/release-tracks/export-schemas.js b/app/lib/release-tracks/export-schemas.js new file mode 100644 index 00000000..f67be824 --- /dev/null +++ b/app/lib/release-tracks/export-schemas.js @@ -0,0 +1,179 @@ +'use strict'; + +// ============================================================================= +// Zod transform schemas for export format transformations. +// +// These schemas encapsulate the DTO transformation logic for each export format. +// Each schema takes a common input shape (snapshot + hydratedObjects) and +// transforms it to the appropriate output format. +// +// Usage: +// const { bundleTransformSchema } = require('./export-schemas'); +// const output = bundleTransformSchema.parse({ snapshot, hydratedObjects }); +// +// See docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md for format specifications. +// ============================================================================= + +const { z } = require('zod'); +const uuid = require('uuid'); + +// ----------------------------------------------------------------------------- +// Shared sub-schemas +// +// These schemas use z.looseObject() to allow additional properties from Mongoose +// documents (e.g., _id, __v) to pass through without validation errors. +// ----------------------------------------------------------------------------- + +const tierEntrySchema = z.looseObject({ + object_ref: z.string(), + object_modified: z.date().or(z.string()), +}); + +const snapshotSchema = z.looseObject({ + id: z.string(), + version: z.string().nullable().optional(), + name: z.string(), + modified: z.date().or(z.string()), + members: z.array(tierEntrySchema).default([]), + staged: z.array(tierEntrySchema).optional(), + candidates: z.array(tierEntrySchema).optional(), +}); + +const hydratedObjectSchema = z.looseObject({ + stix: z.looseObject({}), + workspace: z.looseObject({}).optional(), +}); + +const exportOptionsSchema = z + .object({ + include: z.enum(['staged', 'candidates', 'all']).optional(), + }) + .optional() + .default({}); + +// ----------------------------------------------------------------------------- +// Base input schema (shared by all transforms) +// ----------------------------------------------------------------------------- + +const exportInputSchema = z.object({ + snapshot: snapshotSchema, + hydratedObjects: z.array(hydratedObjectSchema), + options: exportOptionsSchema, +}); + +// ----------------------------------------------------------------------------- +// Helper: Build tier lookup for workbench format +// ----------------------------------------------------------------------------- + +function buildTierLookup(snapshot) { + const lookup = {}; + for (const m of snapshot.members || []) { + lookup[`${m.object_ref}::${new Date(m.object_modified).getTime()}`] = 'released'; + } + for (const s of snapshot.staged || []) { + lookup[`${s.object_ref}::${new Date(s.object_modified).getTime()}`] = 'staged'; + } + for (const c of snapshot.candidates || []) { + lookup[`${c.object_ref}::${new Date(c.object_modified).getTime()}`] = 'candidate'; + } + return lookup; +} + +// ----------------------------------------------------------------------------- +// Bundle Transform Schema +// +// Standard STIX 2.1 bundle format. Only includes `stix` properties - no +// workspace data or workflow metadata. Suitable for external publication. +// ----------------------------------------------------------------------------- + +const bundleTransformSchema = exportInputSchema.transform((input) => ({ + type: 'bundle', + id: `bundle--${uuid.v4()}`, + objects: input.hydratedObjects.map((doc) => doc.stix), +})); + +// ----------------------------------------------------------------------------- +// Workbench Transform Schema +// +// Workbench-optimized format with full metadata. Includes `stix` + `workspace` +// properties and tier annotations. Optimized for Workbench UI consumption. +// ----------------------------------------------------------------------------- + +const workbenchTransformSchema = exportInputSchema.transform((input) => { + const tierLookup = buildTierLookup(input.snapshot); + + const objects = input.hydratedObjects.map((doc) => { + const key = `${doc.stix.id}::${new Date(doc.stix.modified).getTime()}`; + return { + stix: doc.stix, + workspace: doc.workspace || {}, + metadata: { + collection_tier: tierLookup[key] || 'released', + object_type: doc.stix.type, + object_name: doc.stix.name || doc.stix.id, + }, + }; + }); + + return { + collection: { + id: input.snapshot.id, + version: input.snapshot.version, + name: input.snapshot.name, + modified: input.snapshot.modified, + }, + objects, + summary: { + released_count: (input.snapshot.members || []).length, + staged_count: (input.snapshot.staged || []).length, + candidate_count: (input.snapshot.candidates || []).length, + }, + }; +}); + +// ----------------------------------------------------------------------------- +// FilesystemStore Transform Schema +// +// STIX FileSystemStore-compatible directory structure. Objects are grouped by +// STIX type, each with a filename and content property. +// ----------------------------------------------------------------------------- + +const filesystemStoreTransformSchema = exportInputSchema.transform((input) => { + const structure = {}; + + for (const doc of input.hydratedObjects) { + const type = doc.stix.type; + if (!structure[type]) structure[type] = []; + structure[type].push({ + filename: `${doc.stix.id}.json`, + content: doc.stix, + }); + } + + return { + format: 'filesystemstore', + track_id: input.snapshot.id, + version: input.snapshot.version, + structure, + }; +}); + +// ============================================================================= +// Exports +// ============================================================================= + +module.exports = { + // Input schemas (for validation/testing) + exportInputSchema, + snapshotSchema, + hydratedObjectSchema, + exportOptionsSchema, + + // Transform schemas + bundleTransformSchema, + workbenchTransformSchema, + filesystemStoreTransformSchema, + + // Helper (exported for testing) + buildTierLookup, +}; diff --git a/app/lib/release-tracks/release-track-schemas.js b/app/lib/release-tracks/release-track-schemas.js index 3997c0f2..99e53857 100644 --- a/app/lib/release-tracks/release-track-schemas.js +++ b/app/lib/release-tracks/release-track-schemas.js @@ -150,7 +150,7 @@ const cronSchema = z const domainParamSchema = z.enum(['enterprise', 'ics', 'mobile']); -const formatQuerySchema = z.enum(['bundle', 'filesystemstore', 'workbench']); +const formatQuerySchema = z.enum(['snapshot', 'bundle', 'filesystemstore', 'workbench']); const includeQuerySchema = z.enum(['staged', 'candidates', 'all']); diff --git a/app/services/release-tracks/export-service.js b/app/services/release-tracks/export-service.js index f96bc681..72a2dec7 100644 --- a/app/services/release-tracks/export-service.js +++ b/app/services/release-tracks/export-service.js @@ -12,11 +12,18 @@ // This service performs cross-service READS (permitted by the event-driven // architecture — see docs/CROSS_SERVICE_READS_PATTERN.md) by querying STIX // repositories directly. It does NOT write to any external repository. +// +// DTO transformations are encapsulated in Zod transform schemas. See +// app/lib/release-tracks/export-schemas.js for schema definitions. // ============================================================================= -const uuid = require('uuid'); const types = require('../../lib/types'); const logger = require('../../lib/logger'); +const { + bundleTransformSchema, + workbenchTransformSchema, + filesystemStoreTransformSchema, +} = require('../../lib/release-tracks/export-schemas'); // --------------------------------------------------------------------------- // Repository map — lazy-loaded to avoid circular dependency issues at startup. @@ -105,102 +112,38 @@ exports.hydrateMembers = async function hydrateMembers(entries) { }; // ============================================================================= -// Format helpers +// Format helpers (delegating to Zod transform schemas) // ============================================================================= -/** - * Build a lookup from (object_ref::object_modified_ms) → tier name. - * Used by the workbench formatter to annotate each object with its tier. - */ -function buildTierLookup(snapshot) { - const lookup = {}; - for (const m of snapshot.members || []) { - lookup[`${m.object_ref}::${new Date(m.object_modified).getTime()}`] = 'released'; - } - for (const s of snapshot.staged || []) { - lookup[`${s.object_ref}::${new Date(s.object_modified).getTime()}`] = 'staged'; - } - for (const c of snapshot.candidates || []) { - lookup[`${c.object_ref}::${new Date(c.object_modified).getTime()}`] = 'candidate'; - } - return lookup; -} - /** * Format as a standard STIX 2.1 bundle. * * Only includes `stix` properties — no workspace data or workflow metadata. + * Transformation logic is defined in export-schemas.js. */ exports.formatAsBundle = function formatAsBundle(snapshot, hydratedObjects) { - return { - type: 'bundle', - id: `bundle--${uuid.v4()}`, - objects: hydratedObjects.map((doc) => doc.stix), - }; + return bundleTransformSchema.parse({ snapshot, hydratedObjects }); }; /** * Format as a workbench-optimized response with full metadata. * * Includes `stix` + `workspace` properties and tier annotations. - * The `include` option controls whether staged/candidates are included. + * Transformation logic is defined in export-schemas.js. */ exports.formatAsWorkbench = function formatAsWorkbench(snapshot, hydratedObjects) { - const tierLookup = buildTierLookup(snapshot); - - const objects = hydratedObjects.map((doc) => { - const key = `${doc.stix.id}::${new Date(doc.stix.modified).getTime()}`; - return { - stix: doc.stix, - workspace: doc.workspace || {}, - metadata: { - collection_tier: tierLookup[key] || 'released', - object_type: doc.stix.type, - object_name: doc.stix.name || doc.stix.id, - }, - }; - }); - - return { - collection: { - id: snapshot.id, - version: snapshot.version, - name: snapshot.name, - modified: snapshot.modified, - }, - objects, - summary: { - released_count: (snapshot.members || []).length, - staged_count: (snapshot.staged || []).length, - candidate_count: (snapshot.candidates || []).length, - }, - }; + return workbenchTransformSchema.parse({ snapshot, hydratedObjects }); }; /** * Format as a FileSystemStore-compatible directory structure. * * Objects are grouped by STIX type, each with a filename and content property. + * Transformation logic is defined in export-schemas.js. * See docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md for specification. */ exports.formatAsFilesystemStore = function formatAsFilesystemStore(snapshot, hydratedObjects) { - const structure = {}; - - for (const doc of hydratedObjects) { - const type = doc.stix.type; - if (!structure[type]) structure[type] = []; - structure[type].push({ - filename: `${doc.stix.id}.json`, - content: doc.stix, - }); - } - - return { - format: 'filesystemstore', - track_id: snapshot.id, - version: snapshot.version, - structure, - }; + return filesystemStoreTransformSchema.parse({ snapshot, hydratedObjects }); }; // ============================================================================= @@ -223,9 +166,6 @@ exports.exportSnapshot = async function exportSnapshot(snapshot, format, options const members = snapshot.members || []; if (format === 'bundle') { - if (members.length === 0) { - return { type: 'bundle', id: `bundle--${uuid.v4()}`, objects: [] }; - } const hydratedMembers = await exports.hydrateMembers(members); return exports.formatAsBundle(snapshot, hydratedMembers); } @@ -240,22 +180,11 @@ exports.exportSnapshot = async function exportSnapshot(snapshot, format, options allRefs.push(...(snapshot.candidates || [])); } - if (allRefs.length === 0) { - return exports.formatAsWorkbench(snapshot, []); - } const hydratedAll = await exports.hydrateMembers(allRefs); return exports.formatAsWorkbench(snapshot, hydratedAll); } if (format === 'filesystemstore') { - if (members.length === 0) { - return { - format: 'filesystemstore', - track_id: snapshot.id, - version: snapshot.version, - structure: {}, - }; - } const hydratedMembers = await exports.hydrateMembers(members); return exports.formatAsFilesystemStore(snapshot, hydratedMembers); } diff --git a/app/services/release-tracks/release-tracks-service.js b/app/services/release-tracks/release-tracks-service.js index 277f8ae8..51c9ff05 100644 --- a/app/services/release-tracks/release-tracks-service.js +++ b/app/services/release-tracks/release-tracks-service.js @@ -53,20 +53,23 @@ exports.importTrack = async function importTrack(_data) { }; // Phase 6: Format-aware snapshot retrieval -// If options.format is specified, the raw snapshot is hydrated and formatted -// via export-service. Otherwise the raw snapshot is returned as before. +// - 'snapshot' format (or no format): returns raw snapshot as stored +// - 'bundle'/'workbench' formats: hydrates and transforms via export-service +// - 'filesystemstore': blocked at controller level (NotImplementedError) exports.getLatestSnapshot = async function getLatestSnapshot(trackId, options) { const snapshot = await snapshotService.getLatestSnapshot(trackId, options); - if (options && options.format) { - return exportService.exportSnapshot(snapshot, options.format, options); + const format = options?.format; + if (format && format !== 'snapshot') { + return exportService.exportSnapshot(snapshot, format, options); } return snapshot; }; exports.getSnapshotByModified = async function getSnapshotByModified(trackId, modified, options) { const snapshot = await snapshotService.getSnapshotByModified(trackId, modified, options); - if (options && options.format) { - return exportService.exportSnapshot(snapshot, options.format, options); + const format = options?.format; + if (format && format !== 'snapshot') { + return exportService.exportSnapshot(snapshot, format, options); } return snapshot; }; From 0a793b13658d80aba7a277338e462f1254559e23 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:34:16 -0400 Subject: [PATCH 218/370] feat: refactor ADM validation into service-layer ETL pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move ADM validation from early-stage middleware into a late-stage step within `create()` and `updateFull()`, restructured as ETL pipelines: Analyze → Compose → Set Server Fields → Hooks → Validate → Persist. - Remove `validation-middleware.js` and its imports from 15 route files - Relocate ADM validation logic into `base.service.js` as the pipeline's validate step, ensuring validation runs against fully composed objects - Strip server-controlled fields (`x_mitre_attack_spec_version`, `x_mitre_modified_by_ref`, ATT&CK external references) instead of rejecting them, with scaffolding for future server control of `id`, `created`, and `modified` - Add `?dryRun=true` query parameter that short-circuits persistence in the actual create/update pipeline, replacing the deprecated `POST /api/validate` endpoint and eliminating validation/service drift - Surface ADM validation warnings (e.g., non-standard `x_mitre_shortname`) in the response body alongside `stix` and `workspace` - Add `toJSON`/`toObject` transforms to the base Mongoose schema and query-level exclusions to strip `_id`, `__v`, `__t` from all responses - Define `dryRun` as a reusable OpenAPI component in `query-parameters.yml` --- .../components/query-parameters.yml | 14 + app/api/definitions/openapi.yml | 5 + app/api/definitions/paths/tactics-paths.yml | 3 + .../definitions/paths/techniques-paths.yml | 2 + app/controllers/tactics-controller.js | 27 +- app/controllers/techniques-controller.js | 52 +- app/controllers/validate-controller.js | 2 + app/lib/error-handler.js | 2 + app/lib/validation-middleware.js | 208 ------- app/lib/validation-schemas.js | 66 +++ app/models/attack-object-model.js | 16 + app/repository/_base.repository.js | 32 +- app/routes/analytics-routes.js | 15 +- app/routes/assets-routes.js | 15 +- app/routes/campaigns-routes.js | 15 +- app/routes/collections-routes.js | 8 +- app/routes/data-components-routes.js | 3 - app/routes/data-sources-routes.js | 9 +- app/routes/detection-strategies-routes.js | 4 - app/routes/groups-routes.js | 16 +- app/routes/identities-routes.js | 15 +- app/routes/matrices-routes.js | 16 +- app/routes/mitigations-routes.js | 9 +- app/routes/relationships-routes.js | 3 - app/routes/software-routes.js | 15 +- app/routes/tactics-routes.js | 15 +- app/routes/techniques-routes.js | 15 +- app/services/meta-classes/base.service.js | 531 ++++++++++-------- app/services/meta-classes/hooks.service.js | 3 +- app/services/stix/analytics-service.js | 3 +- app/services/stix/data-components-service.js | 3 +- .../stix/detection-strategies-service.js | 3 +- app/services/system/validate-service.js | 114 +--- .../analytics/analytics-includeRefs.spec.js | 3 - .../analytics/analytics-pagination.spec.js | 1 - app/tests/api/analytics/analytics.spec.js | 2 - .../detection-strategies-pagination.spec.js | 1 - .../detection-strategies-spec.js | 3 - app/tests/lib/embedded-relationships.spec.js | 7 - .../adm-validation-middleware.spec.js | 266 ++++++++- app/tests/shared/clone-for-create.js | 5 + 41 files changed, 788 insertions(+), 759 deletions(-) create mode 100644 app/api/definitions/components/query-parameters.yml delete mode 100644 app/lib/validation-middleware.js diff --git a/app/api/definitions/components/query-parameters.yml b/app/api/definitions/components/query-parameters.yml new file mode 100644 index 00000000..c0bc2972 --- /dev/null +++ b/app/api/definitions/components/query-parameters.yml @@ -0,0 +1,14 @@ +components: + parameters: + dryRun: + name: dryRun + in: query + description: | + When set to `true`, the request runs through the full composition and validation + pipeline but does not persist changes. Returns the composed object that would have + been created or updated. + + Use this to validate data before committing it. Replaces the deprecated `POST /api/validate` endpoint. + schema: + type: boolean + default: false diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 391e7081..86c6a957 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -71,6 +71,11 @@ tags: - name: 'Release Tracks' description: 'Operations on release tracks (versioned STIX object releases with workflow management)' +components: + parameters: + dryRun: + $ref: 'components/query-parameters.yml#/components/parameters/dryRun' + paths: # ATT&CK Objects /api/attack-objects: diff --git a/app/api/definitions/paths/tactics-paths.yml b/app/api/definitions/paths/tactics-paths.yml index 212785e9..c810c02a 100644 --- a/app/api/definitions/paths/tactics-paths.yml +++ b/app/api/definitions/paths/tactics-paths.yml @@ -111,6 +111,8 @@ paths: If the `stix.id` property is not set, it creates a new tactic, generating a STIX id for it. tags: - 'Tactics' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -238,6 +240,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index 93968364..6d8dc412 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -136,6 +136,7 @@ paths: type: string pattern: '^T\d{4}$' example: 'T1234' + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -263,6 +264,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/controllers/tactics-controller.js b/app/controllers/tactics-controller.js index 9dd1d960..cd5720c9 100644 --- a/app/controllers/tactics-controller.js +++ b/app/controllers/tactics-controller.js @@ -86,19 +86,23 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { - // Get the data from the request +exports.create = async function (req, res, next) { const tacticData = req.body; const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; // Create the tactic try { const tactic = await tacticsService.create(tacticData, options); + if (options.dryRun) { + return res.status(200).send(tactic); + } + logger.debug('Success: Created tactic with id ' + tactic.stix.id); return res.status(201).send(tactic); } catch (err) { @@ -108,32 +112,35 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create tactic. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create tactic. Server error.'); + return next(err); } } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const tacticData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { const tactic = await tacticsService.updateFull( req.params.stixId, req.params.modified, tacticData, + options, ); if (!tactic) { return res.status(404).send('tactic not found.'); - } else { - logger.debug('Success: Updated tactic with id ' + tactic.stix.id); + } + + if (options.dryRun) { return res.status(200).send(tactic); } + + logger.debug('Success: Updated tactic with id ' + tactic.stix.id); + return res.status(200).send(tactic); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update tactic. Server error.'); + return next(err); } }; diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index efb42004..9f1493bb 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -89,21 +89,23 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { - // Get the data from the request - const techniqueData = req.body; +exports.create = async function (req, res, next) { const options = { import: false, userAccountId: req.user?.userAccountId, - parentTechniqueId: req.query.parentTechniqueId, // NOTE this is new! + parentTechniqueId: req.query.parentTechniqueId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the technique try { - const technique = await techniquesService.create(techniqueData, options); + const result = await techniquesService.create(req.body, options); - logger.debug('Success: Created technique with id ' + technique.stix.id); - return res.status(201).send(technique); + if (options.dryRun) { + return res.status(200).send(result); + } + + logger.debug('Success: Created technique with id ' + result.stix.id); + return res.status(201).send(result); } catch (err) { if (err instanceof DuplicateIdError) { logger.warn('Duplicate stix.id and stix.modified'); @@ -111,37 +113,33 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create technique. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create technique. Server error.'); + return next(err); } } }; -exports.updateFull = async function (req, res) { - // Get the data from the request - const techniqueData = req.body; +exports.updateFull = async function (req, res, next) { + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { - // Create the technique - const technique = await techniquesService.updateFull( + const result = await techniquesService.updateFull( req.params.stixId, req.params.modified, - techniqueData, + req.body, + options, ); - if (!technique) { + if (!result) { return res.status(404).send('Technique not found.'); - } else { - logger.debug('Success: Updated technique with id ' + technique.stix.id); - return res.status(200).send(technique); } - } catch (err) { - if (err.name === 'ImmutablePropertyError') { - logger.warn(`Attempt to modify immutable property: ${err.message}`); - return res.status(400).send(err.message); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update technique. Server error.'); + + if (options.dryRun) { + return res.status(200).send(result); } + + logger.debug('Success: Updated technique with id ' + result.stix.id); + return res.status(200).send(result); + } catch (err) { + return next(err); } }; diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js index 88832a5a..bda4efc6 100644 --- a/app/controllers/validate-controller.js +++ b/app/controllers/validate-controller.js @@ -32,6 +32,8 @@ exports.validate = async function (req, res) { return res.status(StatusCodes.BAD_REQUEST).json({ errors }); } else { const result = validateService.validateStixObject(req.body); + result.deprecated = true; + result.deprecationNotice = 'Use ?dryRun=true on POST/PUT endpoints instead.'; res.json(result); } } catch (error) { diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index 2294b1fb..1267156d 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -29,6 +29,7 @@ const { InvalidTypeError, ImmutablePropertyError, InvalidPostOperationError, + ValidationError, DefaultMarkingDefinitionsNotFoundError, AlreadyReleasedError, InvalidVersionError, @@ -93,6 +94,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof PropertyNotAllowedError || err instanceof CannotUpdateStaticObjectError || err instanceof BadRequestError || + err instanceof ValidationError || err instanceof InvalidVersionError || err instanceof NoTaggedSnapshotsError || err instanceof InvalidComponentTypeError diff --git a/app/lib/validation-middleware.js b/app/lib/validation-middleware.js deleted file mode 100644 index 775460df..00000000 --- a/app/lib/validation-middleware.js +++ /dev/null @@ -1,208 +0,0 @@ -'use strict'; - -const { z, ZodError } = require('zod'); -const { StatusCodes } = require('http-status-codes'); -const logger = require('../lib/logger'); -const { processValidationIssues } = require('../services/system/validate-service'); -const { getSchema } = require('../services/system/validate-service'); -const { - createAttackIdSchema, - stixTypeToAttackIdMapping, -} = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-id'); -const { BadRequestError } = require('../exceptions'); -/** - * Basic workspace schema (without rigid attack ID validation) - * @type {z.ZodObject} - */ -const workspaceSchema = z.object({ - workflow: z - .object({ - state: z.enum(['work-in-progress', 'awaiting-review', 'reviewed', 'static']), - }) - .optional(), - attackId: z.string().optional(), - collections: z - .array( - z.object({ - collection_ref: z.string(), - collection_modified: z.iso.datetime(), - }), - ) - .optional(), -}); - -/** - * Creates a workspace schema with dynamic attackId validation based on STIX type - * @param {string} stixType - The STIX type (e.g., 'x-mitre-tactic') - * @returns {z.ZodObject} Workspace schema with appropriate attackId validation - */ -function createWorkspaceSchema(stixType) { - logger.debug('Creating workspace schema for STIX type:', { stixType }); - - // Check if this STIX type has an associated attack ID pattern - const hasAttackId = stixType in stixTypeToAttackIdMapping; - logger.debug('STIX type attack ID support:', { stixType, hasAttackId }); - - // Add attackId validation only if this STIX type supports attack IDs - if (hasAttackId) { - logger.debug('Adding dynamic attackId validation for STIX type:', { stixType }); - return workspaceSchema.extend({ - attackId: createAttackIdSchema(stixType).optional(), - }); - } - - // For STIX types without attack IDs, use the basic schema - logger.debug('Using basic workspace schema (no attackId validation) for STIX type:', { - stixType, - }); - return workspaceSchema; -} - -/** - * Middleware for validating the request body against a pre-composed STIX schema. - * Wraps the STIX schema with a workspace schema and parses the request body. - * @param {z.ZodObject} stixSchema - Pre-composed STIX schema (with omit/partial/checks already applied) - * @param {Object} options - Configuration options - * @param {boolean} options.enabled - Whether validation is enabled (defaults to true) - * @returns {Function} Express middleware function - */ -function middleware(stixSchema, options = {}) { - const { enabled = true } = options; - - return (req, res, next) => { - // Skip validation if disabled - if (!enabled) { - logger.debug('Workspace STIX validation is disabled, skipping'); - return next(); - } - - logger.debug('Starting workspace+STIX validation middleware'); - - logger.debug('Request body structure:', { - hasWorkspace: !!req.body?.workspace, - hasStix: !!req.body?.stix, - bodyKeys: Object.keys(req.body || {}), - workflowState: req.body?.workspace?.workflow?.state, - }); - - try { - const stixType = req.body?.stix?.type; - - // Wrap the pre-composed STIX schema with the workspace schema - const combinedSchema = z.object({ - workspace: createWorkspaceSchema(stixType), - stix: stixSchema, - }); - - logger.debug('Attempting to parse request body with combined schema'); - combinedSchema.parse(req.body); - - logger.debug('Validation successful, proceeding to next middleware'); - next(); - } catch (error) { - logger.debug('Validation failed:', { - errorType: error.constructor.name, - isZodError: error instanceof ZodError, - }); - - if (error instanceof ZodError) { - // Extract STIX type from request body for error-to-warning conversion - const stixType = req.body?.stix?.type; - - // Process validation issues using shared logic to separate errors from warnings - const { errors, warnings } = processValidationIssues(error.issues, stixType); - - logger.debug('Processed validation issues:', { - issueCount: error.issues?.length, - errorCount: errors.length, - warningCount: warnings.length, - errors, - warnings, - }); - - // Only block the request if there are actual errors (warnings are OK) - if (errors.length > 0) { - logger.info('Request validation failed', { - endpoint: req.path, - method: req.method, - validationErrors: errors, - validationWarnings: warnings, - }); - - res.status(StatusCodes.BAD_REQUEST).json({ - error: 'Invalid data', - details: errors, - warnings: warnings.length > 0 ? warnings : undefined, - }); - } else { - // Only warnings, allow the request to proceed - logger.info('Request validation passed with warnings', { - endpoint: req.path, - method: req.method, - validationWarnings: warnings, - }); - - // Attach warnings to request for potential use by controllers - req.validationWarnings = warnings; - next(); - } - } else { - logger.error('Validation middleware error:', { - error: error.message, - stack: error.stack, - endpoint: req.path, - method: req.method, - }); - res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' }); - } - } - }; -} - -/** - * Pre-configured validation middleware factory that uses runtime configuration. - * The middleware reads the config value at request time to support dynamic config changes (e.g., during tests). - * - * @param {string|string[]} expectedStixType - The STIX type(s) this endpoint accepts - * (e.g. "attack-pattern" or ["tool", "malware"] for software) - * @returns {Function} Express middleware function - */ -function validateWorkspaceStixData(expectedStixType) { - const allowedTypes = Array.isArray(expectedStixType) ? expectedStixType : [expectedStixType]; - - return (req, res, next) => { - // Read config at request time to allow dynamic changes - const config = require('../config/config'); - const enabled = config.validateRequests.withAttackDataModel; - const requestStixType = req.body?.stix?.type; - const workflowState = req.body?.workspace?.workflow?.state || 'reviewed'; - - // Verify the request's STIX type is one this endpoint accepts - if (!allowedTypes.includes(requestStixType)) { - return next( - new BadRequestError( - `Unexpected STIX type "${requestStixType}". This endpoint accepts: ${allowedTypes.join(', ')}`, - ), - ); - } - - const finalSchema = getSchema(requestStixType, workflowState); - if (!finalSchema) { - return next( - new BadRequestError( - `No schema found for STIX type "${requestStixType}". Request body is probably invalid.`, - ), - ); - } - - const middlewareFn = middleware(finalSchema, { enabled }); - return middlewareFn(req, res, next); - }; -} - -module.exports = { - /** Express middleware factory for workspace+STIX validation */ - validateWorkspaceStixData, - /** Basic workspace schema without dynamic attackId validation */ - workspaceSchema, -}; diff --git a/app/lib/validation-schemas.js b/app/lib/validation-schemas.js index 7b8e402a..902824af 100644 --- a/app/lib/validation-schemas.js +++ b/app/lib/validation-schemas.js @@ -102,6 +102,72 @@ const STIX_SCHEMAS = { 'x-mitre-collection': collectionSchema, }; +/** + * Configuration for transforming validation errors (to warnings or suppression). + * These rules handle errors produced by ADM schemas for server-controlled fields that + * clients cannot or should not set. They are used by both the validate endpoint and the + * service layer's post-composition validation. + * + * On a fully-composed object (service layer), suppression rules naturally don't fire + * because server-controlled fields are already populated — no missing-field errors occur. + * On a pre-composed object (validate endpoint), suppression rules fire for fields + * the server will generate, preventing false negatives. + * + * Rule schema: + * fieldPath - Zod error path to match (e.g., ['stix', 'x_mitre_attack_spec_version']) + * errorCode - Zod error code to match (e.g., 'invalid_type', 'invalid_value') + * stixType - Which STIX types: a string, an array, or 'all' + * suppressError - If true, the error is silently dropped + * warningMessage - If set (and suppressError is falsy), convert to warning with this message + * status - (Optional, future use) Which workflow states the rule applies to + */ +const ERROR_TRANSFORMATION_RULES = [ + // Server always sets x_mitre_attack_spec_version + { + fieldPath: ['x_mitre_attack_spec_version'], + errorCode: 'invalid_type', + stixType: 'all', + suppressError: true, + }, + // Server sets x_mitre_modified_by_ref based on authenticated user - user does not need to supply it + { + fieldPath: ['x_mitre_modified_by_ref'], + errorCode: 'invalid_value', + stixType: 'all', + suppressError: true, + }, + // Warn about non-standard tactic shortnames + { + fieldPath: ['x_mitre_shortname'], + errorCode: 'invalid_value', + stixType: 'x-mitre-tactic', + warningMessage: + 'Tactic shortname does not match predefined ATT&CK tactics. This may prevent compatibility with official ATT&CK data but can be used for custom taxonomies.', + }, + // Server sets x_mitre_domains for certain types (assigned during bundle export) + { + fieldPath: ['x_mitre_domains'], + errorCode: 'invalid_type', + stixType: ['intrusion-set', 'campaign', 'x-mitre-matrix', 'x-mitre-detection-strategy'], + suppressError: true, + }, + // Server sets object_marking_refs for certain types + { + fieldPath: ['object_marking_refs'], + errorCode: 'invalid_type', + stixType: ['campaign', 'identity'], + suppressError: true, + }, + // Server sets created_by_ref for certain types + { + fieldPath: ['created_by_ref'], + errorCode: 'invalid_type', + stixType: ['campaign', 'x-mitre-matrix', 'x-mitre-asset', 'course-of-action'], + suppressError: true, + }, +]; + module.exports = { STIX_SCHEMAS, + ERROR_TRANSFORMATION_RULES, }; diff --git a/app/models/attack-object-model.js b/app/models/attack-object-model.js index c91f274d..2349b451 100644 --- a/app/models/attack-object-model.js +++ b/app/models/attack-object-model.js @@ -18,6 +18,22 @@ const attackObjectDefinition = { // Create the schema const options = { collection: 'attackObjects', + toJSON: { + transform(_doc, ret) { + delete ret._id; + delete ret.__v; + delete ret.__t; + return ret; + }, + }, + toObject: { + transform(_doc, ret) { + delete ret._id; + delete ret.__v; + delete ret.__t; + return ret; + }, + }, }; const attackObjectSchema = new mongoose.Schema(attackObjectDefinition, options); diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index 82710a5c..ba6664ef 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -97,6 +97,10 @@ class BaseRepository extends AbstractRepository { aggregation.push({ $limit: options.limit }); } + // Aggregation bypasses Mongoose toJSON/toObject transforms, so we + // must strip internal fields explicitly via $project. + aggregation.push({ $project: { _id: 0, __v: 0, __t: 0 } }); + // Retrieve the documents const documents = await this.model.aggregate(aggregation).exec(); @@ -153,6 +157,10 @@ class BaseRepository extends AbstractRepository { { $match: query }, ]; + // Aggregation bypasses Mongoose toJSON/toObject transforms, so we + // must strip internal fields explicitly via $project. + aggregation.push({ $project: { _id: 0, __v: 0, __t: 0 } }); + // Bundle export needs ALL matching documents, not a paginated subset const documents = await this.model.aggregate(aggregation).exec(); @@ -181,7 +189,14 @@ class BaseRepository extends AbstractRepository { async retrieveAllById(stixId) { try { - return await this.model.find({ 'stix.id': stixId }).sort('-stix.modified').lean().exec(); + // .lean() bypasses Mongoose toJSON/toObject transforms, so .select() + // is needed to exclude internal fields at the query level. + return await this.model + .find({ 'stix.id': stixId }) + .sort('-stix.modified') + .select('-_id -__v -__t') + .lean() + .exec(); } catch (err) { throw new DatabaseError(err); } @@ -189,7 +204,12 @@ class BaseRepository extends AbstractRepository { async retrieveLatestByStixIdLean(stixId) { try { - return await this.model.findOne({ 'stix.id': stixId }).sort('-stix.modified').lean().exec(); + return await this.model + .findOne({ 'stix.id': stixId }) + .sort('-stix.modified') + .select('-_id -__v -__t') + .lean() + .exec(); } catch (err) { throw new DatabaseError(err); } @@ -240,7 +260,7 @@ class BaseRepository extends AbstractRepository { })); // Use cursor for true streaming - const cursor = this.model.find({ $or: conditions }).lean().cursor(); + const cursor = this.model.find({ $or: conditions }).select('-_id -__v -__t').lean().cursor(); let count = 0; for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) { @@ -309,7 +329,11 @@ class BaseRepository extends AbstractRepository { 'stix.modified': object_modified, })); - const documents = await this.model.find({ $or: conditions }).lean().exec(); + const documents = await this.model + .find({ $or: conditions }) + .select('-_id -__v -__t') + .lean() + .exec(); const queryTime = Date.now() - startTime; logger.debug( diff --git a/app/routes/analytics-routes.js b/app/routes/analytics-routes.js index ea595023..da15572d 100644 --- a/app/routes/analytics-routes.js +++ b/app/routes/analytics-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const analyticsController = require('../controllers/analytics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), analyticsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-analytic'), - analyticsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), analyticsController.create); router .route('/analytics/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), analyticsController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-analytic'), - analyticsController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), analyticsController.updateFull) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/routes/assets-routes.js b/app/routes/assets-routes.js index f7973ab6..bc434fe7 100644 --- a/app/routes/assets-routes.js +++ b/app/routes/assets-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const assetsController = require('../controllers/assets-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), assetsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-asset'), - assetsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.create); router .route('/assets/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), assetsController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-asset'), - assetsController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), assetsController.deleteVersionById); module.exports = router; diff --git a/app/routes/campaigns-routes.js b/app/routes/campaigns-routes.js index 395006ff..efdb09fb 100644 --- a/app/routes/campaigns-routes.js +++ b/app/routes/campaigns-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const campaignsController = require('../controllers/campaigns-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), campaignsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('campaign'), - campaignsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.create); router .route('/campaigns/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), campaignsController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('campaign'), - campaignsController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.updateFull) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/routes/collections-routes.js b/app/routes/collections-routes.js index 487c7688..725dddde 100644 --- a/app/routes/collections-routes.js +++ b/app/routes/collections-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const collectionsController = require('../controllers/collections-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), collectionsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-collection'), - collectionsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), collectionsController.create); router .route('/collections/:stixId') diff --git a/app/routes/data-components-routes.js b/app/routes/data-components-routes.js index 07c6f381..3dbc326f 100644 --- a/app/routes/data-components-routes.js +++ b/app/routes/data-components-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const dataComponentsController = require('../controllers/data-components-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -19,7 +18,6 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-data-component'), dataComponentsController.create, ); @@ -58,7 +56,6 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-data-component'), dataComponentsController.updateFull, ) .delete( diff --git a/app/routes/data-sources-routes.js b/app/routes/data-sources-routes.js index 6b6624b3..f220850f 100644 --- a/app/routes/data-sources-routes.js +++ b/app/routes/data-sources-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const dataSourcesController = require('../controllers/data-sources-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), dataSourcesController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-data-source'), - dataSourcesController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), dataSourcesController.create); router .route('/data-sources/:stixId') @@ -42,7 +36,6 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-data-source'), dataSourcesController.updateFull, ) .delete( diff --git a/app/routes/detection-strategies-routes.js b/app/routes/detection-strategies-routes.js index 81a3239e..97d27600 100644 --- a/app/routes/detection-strategies-routes.js +++ b/app/routes/detection-strategies-routes.js @@ -6,8 +6,6 @@ const detectionStrategiesController = require('../controllers/detection-strategi const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); - const router = express.Router(); router @@ -20,7 +18,6 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-detection-strategy'), detectionStrategiesController.create, ); @@ -47,7 +44,6 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-detection-strategy'), detectionStrategiesController.updateFull, ) .delete( diff --git a/app/routes/groups-routes.js b/app/routes/groups-routes.js index e6ebd122..41b0ad9d 100644 --- a/app/routes/groups-routes.js +++ b/app/routes/groups-routes.js @@ -6,8 +6,6 @@ const groupsController = require('../controllers/groups-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); - const router = express.Router(); router @@ -17,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), groupsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('intrusion-set'), - groupsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.create); router .route('/groups/:stixId') @@ -40,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), groupsController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('intrusion-set'), - groupsController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), groupsController.deleteVersionById); module.exports = router; diff --git a/app/routes/identities-routes.js b/app/routes/identities-routes.js index c8f5ded2..6f80d5dd 100644 --- a/app/routes/identities-routes.js +++ b/app/routes/identities-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const identitiesController = require('../controllers/identities-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), identitiesController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('identity'), - identitiesController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), identitiesController.create); router .route('/identities/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), identitiesController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('identity'), - identitiesController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), identitiesController.updateFull) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/routes/matrices-routes.js b/app/routes/matrices-routes.js index 1801a2a9..20a27c32 100644 --- a/app/routes/matrices-routes.js +++ b/app/routes/matrices-routes.js @@ -6,8 +6,6 @@ const matricesController = require('../controllers/matrices-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); - const router = express.Router(); router @@ -17,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), matricesController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-matrix'), - matricesController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.create); router .route('/matrices/:stixId') @@ -40,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), matricesController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-matrix'), - matricesController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), matricesController.deleteVersionById); router diff --git a/app/routes/mitigations-routes.js b/app/routes/mitigations-routes.js index e508253d..04b7e234 100644 --- a/app/routes/mitigations-routes.js +++ b/app/routes/mitigations-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const mitigationsController = require('../controllers/mitigations-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), mitigationsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('course-of-action'), - mitigationsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), mitigationsController.create); router .route('/mitigations/:stixId') @@ -42,7 +36,6 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('course-of-action'), mitigationsController.updateFull, ) .delete( diff --git a/app/routes/relationships-routes.js b/app/routes/relationships-routes.js index 0fce7577..341c0644 100644 --- a/app/routes/relationships-routes.js +++ b/app/routes/relationships-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const relationshipsController = require('../controllers/relationships-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -19,7 +18,6 @@ router .post( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('relationship'), relationshipsController.create, ); @@ -42,7 +40,6 @@ router .put( authn.authenticate, authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('relationship'), relationshipsController.updateFull, ) .delete( diff --git a/app/routes/software-routes.js b/app/routes/software-routes.js index 539cc1d7..c1707464 100644 --- a/app/routes/software-routes.js +++ b/app/routes/software-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const softwareController = require('../controllers/software-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), softwareController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(['tool', 'malware']), - softwareController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.create); router .route('/software/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), softwareController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData(['tool', 'malware']), - softwareController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteVersionById); module.exports = router; diff --git a/app/routes/tactics-routes.js b/app/routes/tactics-routes.js index ff2a9df3..0e7b6080 100644 --- a/app/routes/tactics-routes.js +++ b/app/routes/tactics-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const tacticsController = require('../controllers/tactics-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), tacticsController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-tactic'), - tacticsController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.create); router .route('/tactics/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), tacticsController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('x-mitre-tactic'), - tacticsController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), tacticsController.deleteVersionById); router diff --git a/app/routes/techniques-routes.js b/app/routes/techniques-routes.js index 4e406ef8..c9908aac 100644 --- a/app/routes/techniques-routes.js +++ b/app/routes/techniques-routes.js @@ -5,7 +5,6 @@ const express = require('express'); const techniquesController = require('../controllers/techniques-controller'); const authn = require('../lib/authn-middleware'); const authz = require('../lib/authz-middleware'); -const { validateWorkspaceStixData } = require('../lib/validation-middleware'); const router = express.Router(); @@ -16,12 +15,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), techniquesController.retrieveAll, ) - .post( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('attack-pattern'), - techniquesController.create, - ); + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.create); router .route('/techniques/:stixId') @@ -39,12 +33,7 @@ router authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), techniquesController.retrieveVersionById, ) - .put( - authn.authenticate, - authz.requireRole(authz.editorOrHigher), - validateWorkspaceStixData('attack-pattern'), - techniquesController.updateFull, - ) + .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.updateFull) .delete( authn.authenticate, authz.requireRole(authz.admin), diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 5a7c7531..578f4a83 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -7,7 +7,6 @@ const attackIdGenerator = require('../../lib/attack-id-generator'); const { createAttackExternalReference, findAttackExternalReference, - validateAttackExternalReference, } = require('../../lib/external-reference-builder'); const { DatabaseError, @@ -16,9 +15,10 @@ const { InvalidQueryStringParameterError, InvalidTypeError, OrganizationIdentityNotSetError, - ImmutablePropertyError, InvalidPostOperationError, + ValidationError, } = require('../../exceptions'); +const { getSchema, processValidationIssues } = require('../system/validate-service'); const ServiceWithHooks = require('./hooks.service'); // Import required repositories @@ -315,222 +315,311 @@ class BaseService extends ServiceWithHooks { return documents; } - // TODO add JSDoc - // explain what the method handles - // calls beforeCreate --> {own create logic} --> afterCreate --> emitCreatedEvent - async create(data, options) { - if (data?.stix?.type !== this.type) { - throw new InvalidTypeError(); - } - - options = options || {}; - - if (!options.import) { - // Extracting some fields from the payload - we will need these later - const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( - data.stix, - ); - const attackIdInWorkspace = data.workspace?.attack_id; - const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; - const parentTechniqueId = options?.parentTechniqueId; - - // Check if we're creating a new version of an existing object (same stix.id) - let existingVersion = null; - if (data.stix?.id) { - // Look for any existing version with the same stix.id - const existingVersions = await this.repository.retrieveAllById(data.stix.id); - if (existingVersions && existingVersions.length > 0) { - existingVersion = existingVersions[0]; // Get any version to extract the attack_id - logger.debug( - `Found existing version(s) with stix.id: ${data.stix.id}, will reuse attack_id: ${existingVersion.workspace?.attack_id}`, - ); - } - } - - // SECTION START: CHECKING ILLEGAL OPS - if (existingVersion) { - // POST for existing object (new version/snapshot): Allow client to provide attack_id if it matches - const existingAttackId = existingVersion.workspace?.attack_id; + /** + * Fields that are always server-controlled, regardless of STIX type or operation. + * Declared as a static class property for discoverability and future expansion. + * + * Note: Fields like `created_by_ref`, `x_mitre_modified_by_ref`, and `object_marking_refs` + * are intentionally NOT here — they are only server-controlled in certain contexts + * (e.g., new objects only, specific STIX types) or use merge semantics (marking definitions). + * Their handling remains in the existing composition logic of create() and updateFull(). + * + * Future additions: 'id', 'created', 'modified' (when server takes control of these) + */ + static ALWAYS_STRIPPED_STIX_FIELDS = ['x_mitre_attack_spec_version']; - if (attackIdInWorkspace && attackIdInWorkspace !== existingAttackId) { - logger.warn( - `Immutable property: user attempted to change workspace.attack_id from ${existingAttackId} to ${attackIdInWorkspace}`, - ); - throw new ImmutablePropertyError('workspace.attack_id', { - details: `Expected '${existingAttackId}' but received '${attackIdInWorkspace}'`, - }); - } + /** + * Silently strips universally server-controlled fields from client input. + * + * The API is idempotent with respect to these fields: clients can send them + * and they'll be ignored. The server always composes the correct values during + * the subsequent composition and set-server-controlled-fields pipeline stages. + * + * @param {Object} data - The incoming request data ({ stix, workspace }) + * @param {Object} [options] - Options + * @param {boolean} [options.preserveAttackId] - If true, preserve workspace.attack_id + * and ATT&CK external references (plumbing for future admin override scenarios) + */ + stripServerControlledFields(data, options = {}) { + const stix = data.stix; + if (!stix) return; - if (attackIdInExternalReferences && attackIdInExternalReferences !== existingAttackId) { - logger.warn( - `Immutable property: user attempted to change external_references[0].external_id from ${existingAttackId} to ${attackIdInExternalReferences}`, - ); - throw new ImmutablePropertyError('external_references[0].external_id', { - details: `Expected '${existingAttackId}' but received '${attackIdInExternalReferences}'`, - }); - } + // Strip universally server-controlled STIX fields + for (const field of BaseService.ALWAYS_STRIPPED_STIX_FIELDS) { + delete stix[field]; + } - // Client provided matching values or no values - both are fine - // We'll use the existing attack_id - } else { - // POST for new object: Reject any client-provided attack_id - if (attackIdInExternalReferences) { - logger.warn( - 'Immutable property: user attempted to set backend-controlled property, external_references.0.external_id', - ); - throw new ImmutablePropertyError('external_references.0.external_id'); - } else if (attackIdInWorkspace) { - logger.warn( - 'Immutable property: user attempted to set backend-controlled property, workspace.attack_id', - ); - throw new ImmutablePropertyError('workspace.attack_id'); - } + if (!options.preserveAttackId) { + // Strip workspace.attack_id — server generates/carries forward + if (data.workspace) { + delete data.workspace.attack_id; } - if (data.stix?.external_references) { - // Filter out any MITRE ATT&CK external references (we'll add the correct one below) - data.stix.external_references = data.stix.external_references.filter( + // Filter ATT&CK source refs from external_references; preserve user-provided refs. + // The server will generate the correct ATT&CK ref and prepend it at index 0. + if (stix.external_references) { + stix.external_references = stix.external_references.filter( (ref) => !config.attackSourceNames.includes(ref.source_name), ); - } else { - data.stix.external_references = []; } - // SECTION END: CHECKING ILLEGAL OPS - - // Generate or reuse the ATT&CK ID - if (attackIdGenerator.requiresAttackId(this.type)) { - let attackId; - - if (existingVersion) { - // Reuse the attack_id from the existing version - attackId = existingVersion.workspace.attack_id; - logger.debug(`Reusing ATT&CK ID from existing version: ${attackId}`); - } else { - // Validate subtechnique requirements - if (isSubtechnique && !parentTechniqueId) { - const errorMessage = - 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).'; - logger.error(errorMessage); - throw new InvalidPostOperationError(errorMessage); - } - if (!isSubtechnique && parentTechniqueId) { - const errorMessage = - 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).'; - logger.error(errorMessage); - throw new InvalidPostOperationError(errorMessage); - } - - // Generate a new ATT&CK ID - attackId = await attackIdGenerator.generateAttackId( - this.type, - this.repository, - isSubtechnique, - parentTechniqueId, - ); - logger.debug(`Generated new ATT&CK ID: ${attackId}`); - } + } + } - data.workspace = data.workspace || {}; - data.workspace.attack_id = attackId; - } + /** + * Validates the fully-composed STIX object against the ADM schema. + * + * This runs AFTER all server-controlled fields have been populated (external_references, + * x_mitre_attack_spec_version, created_by_ref, etc.) and BEFORE the repository save. + * Because the object is fully composed, the raw ADM schema validates cleanly — + * ERROR_TRANSFORMATION_RULES suppression rules naturally don't fire since the + * server-controlled fields are present. Only warning rules (e.g., x_mitre_shortname) + * may apply. + * + * @param {Object} data - The composed request data ({ stix, workspace }) + * @returns {{ errors: Array, warnings: Array }} Validation results + */ + validateComposedObject(data) { + const empty = { errors: [], warnings: [] }; + if (!config.validateRequests.withAttackDataModel) return empty; - // Generate and add the ATT&CK external reference - const attackRef = createAttackExternalReference(data); - if (attackRef) { - data.stix.external_references.unshift(attackRef); - } - logger.debug( - `Generated and set the MITRE ATT&CK external reference: ${JSON.stringify(attackRef)}`, - ); + const stixType = data.stix?.type; + const status = data.workspace?.workflow?.state || 'reviewed'; - // Set the ATT&CK Spec Version - data.stix.x_mitre_attack_spec_version = - data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; - logger.debug( - `Set the ATT&CK specification version: ${data.stix.x_mitre_attack_spec_version}`, - ); + const schema = getSchema(stixType, status); + if (!schema) return empty; - // Record the user account that created the object - if (options.userAccountId) { - data.workspace.workflow.created_by_user_account = options.userAccountId; - logger.debug(`Recorded the user account that created the object: ${options.userAccountId}`); - } + const result = schema.safeParse(data.stix); + if (result.success) return empty; - // Set the default marking definitions - await this.setDefaultMarkingDefinitionsForObject(data); - logger.debug(`Set the default marking definition for object`); + return processValidationIssues(result.error.issues, stixType); + } - // Get the organization identity - const organizationIdentityRef = await this.retrieveOrganizationIdentityRef(); + /** + * Creates a new STIX object or a new version of an existing object. + * + * Pipeline stages: + * 1. ANALYZE REQUEST — validate type, determine new vs new-version + * 2. COMPOSE OBJECT — strip server-controlled fields, generate ATT&CK ID + external refs + * 3. SET SERVER-CONTROLLED FIELDS — spec version, identity refs, marking definitions + * 4. LIFECYCLE HOOKS — subclass data transformations (beforeCreate) + * 5. VALIDATE WITH ADM — full schema validation on the composed object + * 6. PERSIST — save document, run afterCreate hook, emit event (skip if dryRun) + * + * @param {Object} data - The request data ({ stix, workspace }) + * @param {Object} [options] - Options + * @param {boolean} [options.import] - If true, use the import path (STIX bundle import) + * @param {string} [options.userAccountId] - The authenticated user's account ID + * @param {string} [options.parentTechniqueId] - Parent technique ATT&CK ID (for subtechniques) + * @param {boolean} [options.dryRun] - If true, compose and validate but skip persistence + * @returns {Object} The created document (or composed data if dryRun) with warnings array + */ + async create(data, options) { + options = options || {}; - // Check for an existing object - let existingObject; - if (data.stix.id) { - existingObject = await this.repository.retrieveOneById(data.stix.id); - } + // ────────────────────────────────────────────── + // 1. ANALYZE REQUEST + // ────────────────────────────────────────────── + if (data?.stix?.type !== this.type) { + throw new InvalidTypeError(); + } - if (existingObject) { - // New version of an existing object - // Only set the x_mitre_modified_by_ref property - data.stix.x_mitre_modified_by_ref = organizationIdentityRef; + if (options.import) { + return this._createFromImport(data, options); + } + + // Determine if this is a new object or a new version of an existing object + let existingVersion = null; + if (data.stix?.id) { + // TODO change this to repository's get latest method - there should be a method for that + const existingVersions = await this.repository.retrieveAllById(data.stix.id); + if (existingVersions?.length > 0) { + existingVersion = existingVersions[0]; logger.debug( - 'Found existing object with matching STIX ID - setting x_mitre_modified_by_ref', + `Found existing version(s) with stix.id: ${data.stix.id}, will reuse attack_id: ${existingVersion.workspace?.attack_id}`, ); + } + } + // TODO: diff analysis — compare posted fields vs existingVersion fields + + // ────────────────────────────────────────────── + // 2. COMPOSE OBJECT + // ────────────────────────────────────────────── + this.stripServerControlledFields(data, options); + data.stix.external_references = data.stix.external_references || []; + + // Generate or reuse the ATT&CK ID + if (attackIdGenerator.requiresAttackId(this.type)) { + let attackId; + + if (existingVersion) { + // Reuse the attack_id from the existing version + attackId = existingVersion.workspace.attack_id; + logger.debug(`Reusing ATT&CK ID from existing version: ${attackId}`); } else { - // New object - // Assign a new STIX id if not already provided - if (!data.stix.id) { - data.stix.id = `${data.stix.type}--${uuid.v4()}`; + const isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; + const parentTechniqueId = options?.parentTechniqueId; + + // Validate subtechnique requirements + if (isSubtechnique && !parentTechniqueId) { + throw new InvalidPostOperationError( + 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).', + ); + } + if (!isSubtechnique && parentTechniqueId) { + throw new InvalidPostOperationError( + 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).', + ); } - logger.debug(`Did not find existing object - setting STIX ID: ${data.stix.id}`); - // Set the created_by_ref and x_mitre_modified_by_ref properties - data.stix.created_by_ref = organizationIdentityRef; - logger.debug( - `Did not find existing object - setting created_by_ref: ${data.stix.created_by_ref}`, - ); - data.stix.x_mitre_modified_by_ref = organizationIdentityRef; - logger.debug( - `Did not find existing object - setting modified_by_ref: ${data.stix.x_mitre_modified_by_ref}`, + // Generate a new ATT&CK ID + attackId = await attackIdGenerator.generateAttackId( + this.type, + this.repository, + isSubtechnique, + parentTechniqueId, ); + logger.debug(`Generated new ATT&CK ID: ${attackId}`); } + + data.workspace = data.workspace || {}; + data.workspace.attack_id = attackId; + } + + // Generate and prepend the ATT&CK external reference + const attackRef = createAttackExternalReference(data); + if (attackRef) { + data.stix.external_references.unshift(attackRef); + } + + // ────────────────────────────────────────────── + // 3. SET SERVER-CONTROLLED FIELDS + // ────────────────────────────────────────────── + // 3a. STIX fields + data.stix.x_mitre_attack_spec_version = config.app.attackSpecVersion; + // TODO: data.stix.modified = new Date().toISOString() (when server controls timestamps) + + const organizationIdentityRef = await this.retrieveOrganizationIdentityRef(); + + // Check for an existing object (may differ from existingVersion if stix.id was just generated) + let existingObject; + if (data.stix.id) { + existingObject = await this.repository.retrieveOneById(data.stix.id); + } + + if (existingObject) { + // New version of an existing object — only set modified_by + data.stix.x_mitre_modified_by_ref = organizationIdentityRef; } else { - // IMPORT PATH: When importing STIX bundles, extract ATT&CK ID from external_references - // and propagate it to workspace.attack_id for efficient querying - const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( - data.stix, - ); - if (attackIdInExternalReferences) { - data.workspace = data.workspace || {}; - data.workspace.attack_id = attackIdInExternalReferences; - logger.debug( - `Import path: Extracted ATT&CK ID from external_references and set workspace.attack_id to ${attackIdInExternalReferences}`, - ); + // Brand-new object — set ID, created_by, modified_by + if (!data.stix.id) { + data.stix.id = `${data.stix.type}--${uuid.v4()}`; } + data.stix.created_by_ref = organizationIdentityRef; + data.stix.x_mitre_modified_by_ref = organizationIdentityRef; + } + + // 3b. Metadata fields + if (options.userAccountId) { + data.workspace.workflow.created_by_user_account = options.userAccountId; } + await this.setDefaultMarkingDefinitionsForObject(data); - // LIFECYCLE HOOK: beforeCreate - // Subclasses can prepare data before core creation logic + // ────────────────────────────────────────────── + // 4. LIFECYCLE HOOKS + // ────────────────────────────────────────────── await this.beforeCreate(data, options); - // Core creation: Save the document - const createdDocument = await this.repository.save(data); + // ────────────────────────────────────────────── + // 5. VALIDATE WITH ADM + // ────────────────────────────────────────────── + const { errors, warnings } = this.validateComposedObject(data); + + if (errors.length > 0) { + throw new ValidationError('ADM validation failed', { details: errors, warnings }); + } - // LIFECYCLE HOOK: afterCreate - // Subclasses can handle post-creation logic + // ────────────────────────────────────────────── + // 6. PERSIST (skip if dry-run) + // ────────────────────────────────────────────── + if (options.dryRun) { + return { ...data, warnings }; + } + + const createdDocument = await this.repository.save(data); await this.afterCreate(createdDocument, options); + await this.emitCreatedEvent(createdDocument, options); + + const result = createdDocument.toObject ? createdDocument.toObject() : createdDocument; + result.warnings = warnings; + return result; + } + + /** + * Import path for create(): handles STIX bundle imports where the object + * already has server-controlled fields populated by the source system. + * + * @param {Object} data - The request data ({ stix, workspace }) + * @param {Object} options - Options passed from create() + * @returns {Object} The created document + * @private + */ + async _createFromImport(data, options) { + // Extract ATT&CK ID from external_references and propagate to workspace.attack_id + const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( + data.stix, + ); + if (attackIdInExternalReferences) { + data.workspace = data.workspace || {}; + data.workspace.attack_id = attackIdInExternalReferences; + } + + const { errors, warnings } = this.validateComposedObject(data); + + if (errors.length > 0) { + throw new ValidationError('ADM validation failed', { details: errors, warnings }); + } + + if (options.dryRun) { + return { ...data, warnings }; + } - // EVENT EMISSION: Emit created event for other services to react + await this.beforeCreate(data, options); + const createdDocument = await this.repository.save(data); + await this.afterCreate(createdDocument, options); await this.emitCreatedEvent(createdDocument, options); - return createdDocument; + const result = createdDocument.toObject ? createdDocument.toObject() : createdDocument; + result.warnings = warnings; + return result; } - async updateFull(stixId, stixModified, data) { + /** + * Updates an existing STIX object version in-place. + * + * Pipeline stages: + * 1. ANALYZE REQUEST — retrieve existing document by stixId + modified + * 2. COMPOSE OBJECT — strip server-controlled fields, compose from existing document + * 3. SET SERVER-CONTROLLED FIELDS — (future: bump modified timestamp) + * 4. LIFECYCLE HOOKS — subclass data transformations (beforeUpdate) + * 5. VALIDATE WITH ADM — full schema validation on the composed object + * 6. PERSIST — merge and save document, run afterUpdate hook, emit event (skip if dryRun) + * + * @param {string} stixId - The STIX ID of the object to update + * @param {string} stixModified - The modified timestamp identifying the specific version + * @param {Object} data - The request data ({ stix, workspace }) + * @param {Object} [options] - Options + * @param {boolean} [options.dryRun] - If true, compose and validate but skip persistence + * @returns {Object|null} The updated document (or composed data if dryRun), null if not found + */ + async updateFull(stixId, stixModified, data, options) { + options = options || {}; + + // ────────────────────────────────────────────── + // 1. ANALYZE REQUEST + // ────────────────────────────────────────────── if (!stixId) { throw new MissingParameterError('stixId'); } - if (!stixModified) { throw new MissingParameterError('modified'); } @@ -539,62 +628,66 @@ class BaseService extends ServiceWithHooks { if (!document) { return null; } + // TODO: diff analysis — detect field-level changes vs document + // TODO: if no changes detected, short-circuit (no-op) - // LIFECYCLE HOOK: beforeUpdate - // Subclasses can prepare data before core update logic - await this.beforeUpdate(stixId, stixModified, data, document); + // ────────────────────────────────────────────── + // 2. COMPOSE OBJECT + // ────────────────────────────────────────────── + this.stripServerControlledFields(data, options); - // Handle ATT&CK external reference for UPDATE operations - // On update, clients CAN provide ATT&CK external references, but they must match the existing data - // and cannot be modified - if (data.workspace?.attack_id) { - // Validate that workspace.attack_id hasn't changed - if (data.workspace.attack_id !== document.workspace.attack_id) { - throw new ImmutablePropertyError('workspace.attack_id', { - details: `Expected '${document.workspace.attack_id}' but received '${data.workspace.attack_id}'`, - }); - } + // Compose server-controlled fields from existing document + data.stix.x_mitre_attack_spec_version = document.stix.x_mitre_attack_spec_version; + + if (document.workspace?.attack_id) { + data.workspace = data.workspace || {}; + data.workspace.attack_id = document.workspace.attack_id; } - if (data.stix?.external_references && attackIdGenerator.requiresAttackId(this.type)) { - const clientAttackRef = findAttackExternalReference(data.stix.external_references); - const expectedAttackRef = createAttackExternalReference(document); + // Compose external_references: prepend existing ATT&CK ref onto user's refs + data.stix.external_references = data.stix.external_references || []; + const existingAttackRef = findAttackExternalReference(document.stix.external_references); + if (existingAttackRef) { + data.stix.external_references.unshift(existingAttackRef); + } - if (clientAttackRef && expectedAttackRef) { - // Validate that the client-provided ATT&CK reference matches expectations - const validation = validateAttackExternalReference(clientAttackRef, expectedAttackRef); - if (!validation.isValid) { - throw new ImmutablePropertyError('stix.external_references[0] (ATT&CK reference)', { - details: validation.error, - }); - } + // ────────────────────────────────────────────── + // 3. SET SERVER-CONTROLLED FIELDS + // ────────────────────────────────────────────── + // TODO: bump stix.modified if diff analysis detects changes + // TODO: set x_mitre_modified_by_ref to current user's org identity - // If client didn't provide URL but should have, add it - if (expectedAttackRef.url && !clientAttackRef.url) { - clientAttackRef.url = expectedAttackRef.url; - } - } else if (!clientAttackRef && expectedAttackRef) { - // Client didn't provide ATT&CK reference - add it - data.stix.external_references.unshift(expectedAttackRef); - } + // ────────────────────────────────────────────── + // 4. LIFECYCLE HOOKS + // ────────────────────────────────────────────── + await this.beforeUpdate(stixId, stixModified, data, document, options); + + // ────────────────────────────────────────────── + // 5. VALIDATE WITH ADM + // ────────────────────────────────────────────── + const { errors, warnings } = this.validateComposedObject(data); + + if (errors.length > 0) { + throw new ValidationError('ADM validation failed', { details: errors, warnings }); } + // ────────────────────────────────────────────── + // 6. PERSIST (skip if dry-run) + // ────────────────────────────────────────────── + if (options.dryRun) return { ...data, warnings }; + const newDocument = await this.repository.updateAndSave(document, data); if (newDocument === document) { - // LIFECYCLE HOOK: afterUpdate - // Subclasses can handle post-update logic await this.afterUpdate(newDocument, document); - - // EVENT EMISSION: Emit updated event for other services to react await this.emitUpdatedEvent(newDocument, document); - - // Document successfully saved - return newDocument; + const result = newDocument.toObject ? newDocument.toObject() : newDocument; + result.warnings = warnings; + return result; } else { throw new DatabaseError({ details: 'Document could not be saved', - document, // Pass along the document that could not be saved + document, }); } } diff --git a/app/services/meta-classes/hooks.service.js b/app/services/meta-classes/hooks.service.js index df607706..32217c28 100644 --- a/app/services/meta-classes/hooks.service.js +++ b/app/services/meta-classes/hooks.service.js @@ -80,8 +80,9 @@ class ServiceWithHooks { * @param {string} _stixModified - The modified timestamp * @param {object} _data - The update data * @param {object} _existingDocument - The existing document + * @param {object} [_options] - Options (e.g., { dryRun: true }) */ - async beforeUpdate(_stixId, _stixModified, _data, _existingDocument) { + async beforeUpdate(_stixId, _stixModified, _data, _existingDocument, _options) { // Default: no-op } diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index 0caf7f4f..c74e2b8b 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -305,7 +305,8 @@ class AnalyticsService extends BaseService { * @throws {Exceptions.NotFoundError} If a referenced data component does not exist * @returns {Promise} */ - async beforeUpdate(stixId, stixModified, data, existingDocument) { + // eslint-disable-next-line no-unused-vars + async beforeUpdate(stixId, stixModified, data, existingDocument, options) { // Initialize embedded_relationships if not present if (!data.workspace) { data.workspace = {}; diff --git a/app/services/stix/data-components-service.js b/app/services/stix/data-components-service.js index 3affb2c6..d909399f 100644 --- a/app/services/stix/data-components-service.js +++ b/app/services/stix/data-components-service.js @@ -309,7 +309,8 @@ class DataComponentsService extends BaseService { * @throws {Exceptions.NotFoundError} If the referenced data source does not exist * @returns {Promise} */ - async beforeUpdate(stixId, stixModified, data, existingDocument) { + // eslint-disable-next-line no-unused-vars + async beforeUpdate(stixId, stixModified, data, existingDocument, options) { // Initialize embedded_relationships if not present if (!data.workspace) { data.workspace = {}; diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index e05d4314..f4af0e97 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -165,7 +165,8 @@ class DetectionStrategiesService extends BaseService { * Prepare detection strategy data before update * Detect changes in x_mitre_analytic_refs and update outbound embedded_relationships */ - async beforeUpdate(stixId, stixModified, data, existingDocument) { + // eslint-disable-next-line no-unused-vars + async beforeUpdate(stixId, stixModified, data, existingDocument, options) { this._assertAnalyticRefsAreUnique(data); const oldAnalyticRefs = existingDocument.stix?.x_mitre_analytic_refs || []; diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js index 97eaae4d..c42575c0 100644 --- a/app/services/system/validate-service.js +++ b/app/services/system/validate-service.js @@ -1,62 +1,5 @@ 'use strict'; -const { STIX_SCHEMAS } = require('../../lib/validation-schemas'); -const logger = require('../../lib/logger'); - -/** - * Configuration for transforming validation errors (to warnings or suppression) - * Add new rules here to convert specific validation errors to warnings or suppress them entirely - * - * stixType can be: - * - A single STIX type string (e.g., 'x-mitre-tactic') - * - An array of STIX types (e.g., ['attack-pattern', 'x-mitre-tactic']) - * - 'all' to match any STIX type - */ -const ERROR_TRANSFORMATION_RULES = [ - // x_mitre_modified_by_ref is handled by the backend - { - fieldPath: ['stix', 'x_mitre_modified_by_ref'], - errorCode: 'invalid_value', - stixType: 'all', - suppressError: true, - }, - // Just raise a warning for x_mitre_shortname, letting users know that the value may not comply with the ADM predefined tactic names - { - fieldPath: ['stix', 'x_mitre_shortname'], - errorCode: 'invalid_value', - stixType: 'x-mitre-tactic', - warningMessage: - 'Tactic shortname does not match predefined ATT&CK tactics. This may prevent compatibility with official ATT&CK data but can be used for custom taxonomies.', - }, - // Users cannot set domain membership on some objects - in such cases, x_mitre_domains is set when the content in a STIX bundle - { - fieldPath: ['stix', 'x_mitre_domains'], - errorCode: 'invalid_type', - stixType: ['intrusion-set', 'campaign', 'x-mitre-matrix', 'x-mitre-detection-strategy'], - suppressError: true, - }, - // Users cannot set x_mitre_attack_spec_version - this is handled by the backend - { - fieldPath: ['stix', 'x_mitre_attack_spec_version'], - errorCode: 'invalid_type', - stixType: 'all', - suppressError: true, - }, - // Users cannot set object_marking_refs on campaigns - { - fieldPath: ['stix', 'object_marking_refs'], - errorCode: 'invalid_type', - stixType: ['campaign', 'identity'], - suppressError: true, - }, - { - fieldPath: ['stix', 'created_by_ref'], - errorCode: 'invalid_type', - stixType: ['campaign', 'x-mitre-matrix', 'x-mitre-asset', 'course-of-action'], - suppressError: true, - }, - // Add more rules here as needed -]; -exports.ERROR_TRANSFORMATION_RULES = ERROR_TRANSFORMATION_RULES; +const { STIX_SCHEMAS, ERROR_TRANSFORMATION_RULES } = require('../../lib/validation-schemas'); /** * Get the schema to use for validating a STIX object. @@ -66,64 +9,33 @@ exports.ERROR_TRANSFORMATION_RULES = ERROR_TRANSFORMATION_RULES; * composes the correct schema based on the STIX type and workflow status. * * Composition order (for schemas with checks): - * base → .omit() → .partial() (if WIP) → .check(checks) + * base → .partial() (if WIP) → .check(checks) * * This ordering is critical because Zod v4.3.6+ disallows .omit(), .pick(), * and .partial() on schemas that already have .check() applied. * + * For WIP objects, all fields are made optional via .partial(). + * For non-WIP objects, the raw ADM schema is used as-is. Server-controlled + * field errors are handled post-validation by ERROR_TRANSFORMATION_RULES. + * * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") - * @param {string[]} omitStixFields - Array of STIX field names to omit from validation * @returns {Object|null} Zod schema, or null if the STIX type is unknown */ -function getSchema( - stixType, - status, - omitStixFields = ['x_mitre_attack_spec_version', 'external_references'], -) { +function getSchema(stixType, status) { const admSchemaRef = STIX_SCHEMAS[stixType]; if (!admSchemaRef) return null; - const isPartial = status === 'work-in-progress'; - let stixSchema; + const isWip = status === 'work-in-progress'; if (admSchemaRef.base && admSchemaRef.checks) { - // Schema with refinements: compose in the safe order (omit/partial BEFORE check) - stixSchema = admSchemaRef.base; - - if (omitStixFields.length > 0) { - const omitObject = omitStixFields.reduce((acc, field) => { - acc[field] = true; - return acc; - }, {}); - stixSchema = stixSchema.omit(omitObject); - } - - if (isPartial) { - stixSchema = stixSchema.partial(); - } - - // Re-apply refinements last - stixSchema = stixSchema.check(admSchemaRef.checks); - } else { - // Simple schema (no refinements): safe to call .omit() and .partial() directly - stixSchema = admSchemaRef; - - if (omitStixFields.length > 0) { - const omitObject = omitStixFields.reduce((acc, field) => { - acc[field] = true; - return acc; - }, {}); - stixSchema = stixSchema.omit(omitObject); - } - - if (isPartial) { - stixSchema = stixSchema.partial(); - } + // Schema with refinements: compose in the safe order (partial BEFORE check) + const base = isWip ? admSchemaRef.base.partial() : admSchemaRef.base; + return base.check(admSchemaRef.checks); } - logger.debug('Resolved STIX schema:', { stixType, status, isPartial, omitStixFields }); - return stixSchema; + // Simple schema (no refinements) + return isWip ? admSchemaRef.partial() : admSchemaRef; } exports.getSchema = getSchema; diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index 3c41b146..c1e98b03 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -25,7 +25,6 @@ const analyticData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_log_source_references: [ @@ -53,7 +52,6 @@ const detectionStrategyData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_domains: ['enterprise-attack'], x_mitre_analytic_refs: [], // Will be populated with analytic ID after creation }, @@ -74,7 +72,6 @@ const dataComponentData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', }, }; diff --git a/app/tests/api/analytics/analytics-pagination.spec.js b/app/tests/api/analytics/analytics-pagination.spec.js index df01ba8e..7bd8fc37 100644 --- a/app/tests/api/analytics/analytics-pagination.spec.js +++ b/app/tests/api/analytics/analytics-pagination.spec.js @@ -17,7 +17,6 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '4.0.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ diff --git a/app/tests/api/analytics/analytics.spec.js b/app/tests/api/analytics/analytics.spec.js index f6393d3b..01313d41 100644 --- a/app/tests/api/analytics/analytics.spec.js +++ b/app/tests/api/analytics/analytics.spec.js @@ -29,7 +29,6 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ @@ -349,7 +348,6 @@ describe('Analytics API', function () { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_domains: ['enterprise-attack'], x_mitre_analytic_refs: [], }, diff --git a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js index cb3de3f7..e8946a9e 100644 --- a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js @@ -17,7 +17,6 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '4.0.0', x_mitre_domains: ['enterprise-attack'], /** * NOTE: Do not set embedded relationships (x_mitre_analytic_refs) unless you can diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index ac3ca9ae..ad236a8a 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -27,7 +27,6 @@ const initialPostContentForDetectionStrategy = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_domains: ['enterprise-attack'], x_mitre_analytic_refs: [ 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match! @@ -52,7 +51,6 @@ const initialPostContentForAnalytic1 = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ @@ -84,7 +82,6 @@ const initialPostContentForAnalytic2 = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_platforms: ['windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ diff --git a/app/tests/lib/embedded-relationships.spec.js b/app/tests/lib/embedded-relationships.spec.js index 7169bfcd..c48921bb 100644 --- a/app/tests/lib/embedded-relationships.spec.js +++ b/app/tests/lib/embedded-relationships.spec.js @@ -48,7 +48,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function x_mitre_domains: ['enterprise-attack'], x_mitre_platforms: ['windows'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', }, @@ -80,7 +79,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function x_mitre_domains: ['enterprise-attack'], x_mitre_platforms: ['windows'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', }, @@ -114,7 +112,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function modified: timestamp, x_mitre_domains: ['enterprise-attack'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_analytic_refs: [analytic1.stix.id, analytic2.stix.id], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', @@ -210,7 +207,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function x_mitre_domains: ['enterprise-attack'], x_mitre_platforms: ['windows'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', }, @@ -240,7 +236,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function modified: timestamp2, x_mitre_domains: ['enterprise-attack'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_analytic_refs: [], // Empty initially object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', @@ -342,7 +337,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function x_mitre_domains: ['enterprise-attack'], x_mitre_platforms: ['windows'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', }, @@ -372,7 +366,6 @@ describe('Embedded Relationships - Detection Strategies and Analytics', function modified: timestamp2, x_mitre_domains: ['enterprise-attack'], x_mitre_version: '1.0', - x_mitre_attack_spec_version: '3.3.0', x_mitre_analytic_refs: [analytic4.stix.id], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 8f13211b..47d43425 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -224,7 +224,7 @@ describe('ADM Validation Middleware', function () { // Make a field invalid syntheticStix.x_mitre_is_subtechnique = 'not-a-boolean'; // Should be boolean - const requestBody = { + let requestBody = { workspace: { workflow: { state: 'work-in-progress', @@ -233,15 +233,17 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + requestBody = cloneForCreate(requestBody); + const res = await request(app) .post(endpoint) .send(requestBody) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - // Should fail validation + // Should fail ADM validation in the service layer expect(res.status).toBe(400); - expect(res.body.error).toBeDefined(); + expect(res.body.message).toBeDefined(); expect(res.body.details).toBeDefined(); expect(Array.isArray(res.body.details)).toBe(true); }); @@ -280,7 +282,7 @@ describe('ADM Validation Middleware', function () { // Remove a required field delete syntheticStix.name; - const requestBody = { + let requestBody = { workspace: { workflow: { state: 'reviewed', @@ -289,6 +291,8 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + requestBody = cloneForCreate(requestBody); + const res = await request(app) .post(endpoint) .send(requestBody) @@ -297,7 +301,7 @@ describe('ADM Validation Middleware', function () { // Should fail validation because 'name' is required in full validation expect(res.status).toBe(400); - expect(res.body.error).toBeDefined(); + expect(res.body.message).toBeDefined(); expect(res.body.details).toBeDefined(); expect(Array.isArray(res.body.details)).toBe(true); }); @@ -305,8 +309,36 @@ describe('ADM Validation Middleware', function () { it('should reject data with invalid field values in reviewed state', async function () { const syntheticStix = createSyntheticStix(stixType); - // Make a field invalid - syntheticStix.type = 'invalid-type'; // Wrong type + // Make a field invalid (wrong type for boolean field) + syntheticStix.x_mitre_is_subtechnique = 'not-a-boolean'; + + let requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + requestBody = cloneForCreate(requestBody); + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + // Should fail ADM validation + expect(res.status).toBe(400); + expect(res.body.message).toBe('ADM validation failed'); + }); + + it('should reject data with wrong STIX type for endpoint', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Set wrong STIX type — caught by service before ADM validation + syntheticStix.type = 'invalid-type'; const requestBody = { workspace: { @@ -323,9 +355,8 @@ describe('ADM Validation Middleware', function () { .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); - // Should fail validation + // Should fail with InvalidTypeError (plain string response) expect(res.status).toBe(400); - expect(res.body.message).toBeDefined(); }); }); @@ -455,7 +486,7 @@ describe('ADM Validation Middleware', function () { .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); expect(res.status).toBe(400); - expect(res.body.error).toBeDefined(); + expect(res.body.message).toBeDefined(); }); }); @@ -541,7 +572,7 @@ describe('ADM Validation Middleware', function () { .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); expect(res.status).toBe(400); - expect(res.body.error).toBeDefined(); + expect(res.body.message).toBeDefined(); }); }); @@ -570,11 +601,11 @@ describe('ADM Validation Middleware', function () { .set('Accept', 'application/json') .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`); - // Should NOT return 400 with "Invalid data" error because ADM validation is disabled + // Should NOT return 400 with "ADM validation failed" error because ADM validation is disabled // The request will likely fail at the database level (missing required field), // but it should NOT fail with ADM validation error if (res.status === 400 && res.headers['content-type']?.includes('json')) { - expect(res.body.error).not.toBe('Invalid data'); + expect(res.body.message).not.toBe('ADM validation failed'); } // Re-enable ADM validation @@ -590,7 +621,7 @@ describe('ADM Validation Middleware', function () { // Remove required field delete syntheticStix.name; - const requestBody = { + let requestBody = { workspace: { workflow: { state: 'reviewed', @@ -599,6 +630,8 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + requestBody = cloneForCreate(requestBody); + const res = await request(app) .post(endpoint) .send(requestBody) @@ -607,11 +640,208 @@ describe('ADM Validation Middleware', function () { // Should return 400 with validation error expect(res.status).toBe(400); - expect(res.body.error).toBe('Invalid data'); + expect(res.body.message).toBe('ADM validation failed'); expect(res.body.details).toBeDefined(); }); }); + describe('Server-controlled field stripping', function () { + it('should silently strip x_mitre_attack_spec_version from client input', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Explicitly set a server-controlled field — should be silently stripped + syntheticStix.x_mitre_attack_spec_version = '999.0'; + + let requestBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + requestBody = cloneForCreate(requestBody); + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + // Should succeed — the server strips the field and sets the correct value + expect(res.status).toBe(201); + expect(res.body.stix.x_mitre_attack_spec_version).toBeDefined(); + expect(res.body.stix.x_mitre_attack_spec_version).not.toBe('999.0'); + }); + + it('should silently strip ATT&CK external references from client input', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Add a client-provided ATT&CK ref and a user ref + syntheticStix.external_references = [ + { source_name: 'mitre-attack', external_id: 'T9999', url: 'https://fake.url' }, + { source_name: 'my-source', description: 'User reference' }, + ]; + + let requestBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + requestBody = cloneForCreate(requestBody); + + const res = await request(app) + .post(endpoint) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + // Should succeed — server strips the ATT&CK ref and generates the correct one + expect(res.status).toBe(201); + // The server-generated ATT&CK ref should be at index 0 + expect(res.body.stix.external_references[0].source_name).toBe('mitre-attack'); + // The client's fake ATT&CK ref URL should NOT be present + expect(res.body.stix.external_references[0].url).not.toBe('https://fake.url'); + // The user's custom ref should still be present + const userRef = res.body.stix.external_references.find( + (ref) => ref.source_name === 'my-source', + ); + expect(userRef).toBeDefined(); + }); + }); + + describe('dryRun support', function () { + it('should return composed object without persisting on POST with dryRun=true', async function () { + const syntheticStix = createSyntheticStix(stixType); + + let requestBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + requestBody = cloneForCreate(requestBody); + + const res = await request(app) + .post(`${endpoint}?dryRun=true`) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + expect(res.status).toBe(200); + expect(res.body.stix).toBeDefined(); + expect(res.body.stix.type).toBe(stixType); + // Server-controlled fields should be composed + expect(res.body.stix.x_mitre_attack_spec_version).toBeDefined(); + // Mongoose internals should not be exposed + expect(res.body._id).toBeUndefined(); + expect(res.body.__v).toBeUndefined(); + expect(res.body.__t).toBeUndefined(); + }); + + it('should return validation error on POST with dryRun=true and invalid data', async function () { + const syntheticStix = createSyntheticStix(stixType); + + // Make data invalid — wrong type for a boolean field + syntheticStix.x_mitre_is_subtechnique = 'not-a-boolean'; + + let requestBody = { + workspace: { + workflow: { + state: 'reviewed', + }, + }, + stix: syntheticStix, + }; + + requestBody = cloneForCreate(requestBody); + + const res = await request(app) + .post(`${endpoint}?dryRun=true`) + .send(requestBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + // Should fail ADM validation even in dry-run mode + expect(res.status).toBe(400); + expect(res.body.message).toBe('ADM validation failed'); + }); + + it('should return composed object without persisting on PUT with dryRun=true', async function () { + // First, create an object to update + const syntheticStix = createSyntheticStix(stixType); + + let createBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: syntheticStix, + }; + + createBody = cloneForCreate(createBody); + + const createRes = await request(app) + .post(endpoint) + .send(createBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + const createdObject = createRes.body; + + // Now do a dry-run update + let updateBody = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + ...createdObject.stix, + name: 'Dry Run Updated Name', + }, + }; + + updateBody = cloneForCreate(updateBody); + + const res = await request(app) + .put( + `${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}?dryRun=true`, + ) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + expect(res.status).toBe(200); + expect(res.body.stix).toBeDefined(); + expect(res.body.stix.name).toBe('Dry Run Updated Name'); + // Mongoose internals should not be exposed + expect(res.body._id).toBeUndefined(); + expect(res.body.__v).toBeUndefined(); + expect(res.body.__t).toBeUndefined(); + + // Verify the object was NOT actually persisted by fetching the original + const getRes = await request(app) + .get(`${endpoint}/${createdObject.stix.id}/modified/${createdObject.stix.modified}`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); + + expect(getRes.status).toBe(200); + // Original name should be unchanged + expect(getRes.body.stix.name).not.toBe('Dry Run Updated Name'); + }); + }); + describe('Error response format', function () { it('should return detailed validation errors with proper structure', async function () { const syntheticStix = createSyntheticStix(stixType); @@ -620,7 +850,7 @@ describe('ADM Validation Middleware', function () { delete syntheticStix.name; // Missing required field syntheticStix.x_mitre_is_subtechnique = 'invalid'; // Wrong type - const requestBody = { + let requestBody = { workspace: { workflow: { state: 'reviewed', @@ -629,6 +859,8 @@ describe('ADM Validation Middleware', function () { stix: syntheticStix, }; + requestBody = cloneForCreate(requestBody); + const res = await request(app) .post(endpoint) .send(requestBody) @@ -636,7 +868,7 @@ describe('ADM Validation Middleware', function () { .set('Cookie', `${passportCookie.name}=${passportCookie.value}`); expect(res.status).toBe(400); - expect(res.body.error).toBe('Invalid data'); + expect(res.body.message).toBe('ADM validation failed'); expect(res.body.details).toBeDefined(); expect(Array.isArray(res.body.details)).toBe(true); expect(res.body.details.length).toBeGreaterThan(0); diff --git a/app/tests/shared/clone-for-create.js b/app/tests/shared/clone-for-create.js index 900a0a7e..ffc67f81 100644 --- a/app/tests/shared/clone-for-create.js +++ b/app/tests/shared/clone-for-create.js @@ -39,6 +39,11 @@ function cloneForCreate(attackObject) { delete cloned.workspace.attack_id; } + // Remove server-controlled x_mitre_attack_spec_version (backend sets this) + if (cloned.stix) { + delete cloned.stix.x_mitre_attack_spec_version; + } + // Remove MITRE ATT&CK external references (backend controls these) if (cloned.stix && cloned.stix.external_references) { cloned.stix.external_references = cloned.stix.external_references.filter( From 7e54ef00bcb4c8d5ec31a964a2476ebeac810529 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:22:39 -0400 Subject: [PATCH 219/370] feat: add dryRun support to all remaining relevant CRUD endpoints --- app/api/definitions/paths/analytics-paths.yml | 3 ++ app/api/definitions/paths/assets-paths.yml | 3 ++ app/api/definitions/paths/campaigns-paths.yml | 3 ++ .../paths/data-components-paths.yml | 3 ++ .../definitions/paths/data-sources-paths.yml | 3 ++ .../paths/detection-strategies-paths.yml | 3 ++ app/api/definitions/paths/groups-paths.yml | 3 ++ .../definitions/paths/identities-paths.yml | 3 ++ .../paths/marking-definitions-paths.yml | 3 ++ app/api/definitions/paths/matrices-paths.yml | 3 ++ .../definitions/paths/mitigations-paths.yml | 3 ++ app/api/definitions/paths/notes-paths.yml | 3 ++ .../definitions/paths/relationships-paths.yml | 3 ++ app/api/definitions/paths/software-paths.yml | 3 ++ app/controllers/analytics-controller.js | 11 ++++- app/controllers/assets-controller.js | 33 +++++++++------ app/controllers/campaigns-controller.js | 31 +++++++------- app/controllers/data-components-controller.js | 16 +++++--- app/controllers/data-sources-controller.js | 29 ++++++++------ .../detection-strategies-controller.js | 15 ++++--- app/controllers/groups-controller.js | 32 +++++++++------ app/controllers/identities-controller.js | 29 ++++++++------ .../marking-definitions-controller.js | 40 ++++++++----------- app/controllers/matrices-controller.js | 32 ++++++++------- app/controllers/mitigations-controller.js | 22 +++++----- app/controllers/notes-controller.js | 27 ++++++++----- app/controllers/relationships-controller.js | 29 ++++++++------ app/controllers/software-controller.js | 34 +++++++--------- 28 files changed, 254 insertions(+), 168 deletions(-) diff --git a/app/api/definitions/paths/analytics-paths.yml b/app/api/definitions/paths/analytics-paths.yml index c1d51b96..0e5e9b2c 100644 --- a/app/api/definitions/paths/analytics-paths.yml +++ b/app/api/definitions/paths/analytics-paths.yml @@ -119,6 +119,8 @@ paths: If the `stix.id` property is not set, it creates a new analytic, generating a STIX id for it. tags: - 'Analytics' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -254,6 +256,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index 8da69d7a..b4a649f1 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -123,6 +123,8 @@ paths: If the `stix.id` property is not set, it creates a new asset, generating a STIX id for it. tags: - 'Assets' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -248,6 +250,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/campaigns-paths.yml b/app/api/definitions/paths/campaigns-paths.yml index f4a78d69..3661799b 100644 --- a/app/api/definitions/paths/campaigns-paths.yml +++ b/app/api/definitions/paths/campaigns-paths.yml @@ -99,6 +99,8 @@ paths: If the `stix.id` property is not set, it creates a new campaign, generating a STIX id for it. tags: - 'Campaigns' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -226,6 +228,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/data-components-paths.yml b/app/api/definitions/paths/data-components-paths.yml index 50310db1..cf304548 100644 --- a/app/api/definitions/paths/data-components-paths.yml +++ b/app/api/definitions/paths/data-components-paths.yml @@ -99,6 +99,8 @@ paths: If the `stix.id` property is not set, it creates a new data component, generating a STIX id for it. tags: - 'Data Components' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -280,6 +282,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/data-sources-paths.yml b/app/api/definitions/paths/data-sources-paths.yml index 40ac7b67..a7f7a342 100644 --- a/app/api/definitions/paths/data-sources-paths.yml +++ b/app/api/definitions/paths/data-sources-paths.yml @@ -123,6 +123,8 @@ paths: If the `stix.id` property is not set, it creates a new data source, generating a STIX id for it. tags: - 'Data Sources' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -264,6 +266,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/detection-strategies-paths.yml b/app/api/definitions/paths/detection-strategies-paths.yml index 6d430148..9ff84498 100644 --- a/app/api/definitions/paths/detection-strategies-paths.yml +++ b/app/api/definitions/paths/detection-strategies-paths.yml @@ -111,6 +111,8 @@ paths: If the `stix.id` property is not set, it creates a new detection strategy, generating a STIX id for it. tags: - 'Detection Strategies' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -238,6 +240,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/groups-paths.yml b/app/api/definitions/paths/groups-paths.yml index cfbf7920..a9d76466 100644 --- a/app/api/definitions/paths/groups-paths.yml +++ b/app/api/definitions/paths/groups-paths.yml @@ -99,6 +99,8 @@ paths: If the `stix.id` property is not set, it creates a new group, generating a STIX id for it. tags: - 'Groups' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -226,6 +228,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/identities-paths.yml b/app/api/definitions/paths/identities-paths.yml index b0a8c7d4..edca01cf 100644 --- a/app/api/definitions/paths/identities-paths.yml +++ b/app/api/definitions/paths/identities-paths.yml @@ -80,6 +80,8 @@ paths: If the `stix.id` property is not set, it creates a new identity, generating a STIX id for it. tags: - 'Identities' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -205,6 +207,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/marking-definitions-paths.yml b/app/api/definitions/paths/marking-definitions-paths.yml index fdae62ea..a12814cb 100644 --- a/app/api/definitions/paths/marking-definitions-paths.yml +++ b/app/api/definitions/paths/marking-definitions-paths.yml @@ -72,6 +72,8 @@ paths: The `stix.id` property should not be set; this endpoint will create a new marking definition, generating a STIX id for it. tags: - 'Marking Definitions' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -129,6 +131,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/matrices-paths.yml b/app/api/definitions/paths/matrices-paths.yml index e9ae088e..a3ef13d0 100644 --- a/app/api/definitions/paths/matrices-paths.yml +++ b/app/api/definitions/paths/matrices-paths.yml @@ -99,6 +99,8 @@ paths: If the `stix.id` property is not set, it creates a new matrix, generating a STIX id for it. tags: - 'Matrices' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -226,6 +228,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/mitigations-paths.yml b/app/api/definitions/paths/mitigations-paths.yml index ed5aa1d7..dde55014 100644 --- a/app/api/definitions/paths/mitigations-paths.yml +++ b/app/api/definitions/paths/mitigations-paths.yml @@ -111,6 +111,8 @@ paths: If the `stix.id` property is not set, it creates a new mitigation, generating a STIX id for it. tags: - 'Mitigations' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -238,6 +240,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/notes-paths.yml b/app/api/definitions/paths/notes-paths.yml index 496ac818..c246d8de 100644 --- a/app/api/definitions/paths/notes-paths.yml +++ b/app/api/definitions/paths/notes-paths.yml @@ -98,6 +98,8 @@ paths: If the `stix.id` property is not set, it creates a new note, generating a STIX id for it. tags: - 'Notes' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -224,6 +226,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/relationships-paths.yml b/app/api/definitions/paths/relationships-paths.yml index d1b6bcc0..e25940fc 100644 --- a/app/api/definitions/paths/relationships-paths.yml +++ b/app/api/definitions/paths/relationships-paths.yml @@ -169,6 +169,8 @@ paths: If the `stix.id` property is not set, it creates a new relationship, generating a STIX id for it. tags: - 'Relationships' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -296,6 +298,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/api/definitions/paths/software-paths.yml b/app/api/definitions/paths/software-paths.yml index 922b869b..7a8d7da2 100644 --- a/app/api/definitions/paths/software-paths.yml +++ b/app/api/definitions/paths/software-paths.yml @@ -123,6 +123,8 @@ paths: If the `stix.id` property is not set, it creates a new software object, generating a STIX id for it. tags: - 'Software' + parameters: + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: @@ -249,6 +251,7 @@ paths: required: true schema: type: string + - $ref: '../components/query-parameters.yml#/components/parameters/dryRun' requestBody: required: true content: diff --git a/app/controllers/analytics-controller.js b/app/controllers/analytics-controller.js index e5f9abb3..961a1a20 100644 --- a/app/controllers/analytics-controller.js +++ b/app/controllers/analytics-controller.js @@ -95,11 +95,14 @@ exports.create = async function (req, res, next) { const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; try { - // Create the analytic const analytic = await analyticsService.create(analyticData, options); + if (options.dryRun) { + return res.status(200).send(analytic); + } logger.debug('Success: Created analytic with id ' + analytic.stix.id); return res.status(201).send(analytic); } catch (err) { @@ -109,18 +112,22 @@ exports.create = async function (req, res, next) { }; exports.updateFull = async function (req, res, next) { - // Get the data from the request const analyticData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { const analytic = await analyticsService.updateFull( req.params.stixId, req.params.modified, analyticData, + options, ); if (!analytic) { return res.status(404).send('Analytic not found.'); } + if (options.dryRun) { + return res.status(200).send(analytic); + } logger.debug('Success: Updated analytic with id ' + analytic.stix.id); return res.status(200).send(analytic); } catch (err) { diff --git a/app/controllers/assets-controller.js b/app/controllers/assets-controller.js index 4bb7edce..869b3537 100644 --- a/app/controllers/assets-controller.js +++ b/app/controllers/assets-controller.js @@ -93,15 +93,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const assetData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; const asset = await assetsService.create(assetData, options); + if (options.dryRun) { + return res.status(200).send(asset); + } logger.debug('Success: Created asset with id ' + asset.stix.id); return res.status(201).send(asset); } catch (err) { @@ -117,21 +120,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const assetData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { - const asset = await assetsService.updateFull(req.params.stixId, req.params.modified, assetData); + const asset = await assetsService.updateFull( + req.params.stixId, + req.params.modified, + assetData, + options, + ); if (!asset) { return res.status(404).send('Asset not found.'); - } else { - logger.debug('Success: Updated asset with id ' + asset.stix.id); + } + if (options.dryRun) { return res.status(200).send(asset); } + logger.debug('Success: Updated asset with id ' + asset.stix.id); + return res.status(200).send(asset); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update asset. Server error.'); + return next(err); } }; diff --git a/app/controllers/campaigns-controller.js b/app/controllers/campaigns-controller.js index a9d991c5..24e7ff9f 100644 --- a/app/controllers/campaigns-controller.js +++ b/app/controllers/campaigns-controller.js @@ -89,17 +89,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const campaignData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; - // Create the campaign try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; - const campaign = await campaignsService.create(campaignData, options); + if (options.dryRun) { + return res.status(200).send(campaign); + } logger.debug('Success: Created campaign with id ' + campaign.stix.id); return res.status(201).send(campaign); } catch (err) { @@ -118,25 +119,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const campaignData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; + try { const campaign = await campaignsService.updateFull( req.params.stixId, req.params.modified, campaignData, + options, ); if (!campaign) { return res.status(404).send('Campaign not found.'); - } else { - logger.debug('Success: Updated campaign with id ' + campaign.stix.id); + } + if (options.dryRun) { return res.status(200).send(campaign); } + logger.debug('Success: Updated campaign with id ' + campaign.stix.id); + return res.status(200).send(campaign); } catch (err) { - // Create the campaign - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update campaign. Server error.'); + return next(err); } }; diff --git a/app/controllers/data-components-controller.js b/app/controllers/data-components-controller.js index 44867526..9a89f23d 100644 --- a/app/controllers/data-components-controller.js +++ b/app/controllers/data-components-controller.js @@ -108,11 +108,14 @@ exports.create = async function (req, res, next) { const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the data component try { const dataComponent = await dataComponentsService.create(dataComponentData, options); + if (options.dryRun) { + return res.status(200).send(dataComponent); + } logger.debug('Success: Created data component with id ' + dataComponent.stix.id); return res.status(201).send(dataComponent); } catch (err) { @@ -121,23 +124,24 @@ exports.create = async function (req, res, next) { }; exports.updateFull = async function (req, res, next) { - // Get the data from the request const dataComponentData = req.body; - - // Create the data component + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { const dataComponent = await dataComponentsService.updateFull( req.params.stixId, req.params.modified, dataComponentData, + options, ); if (!dataComponent) { return res.status(404).send('Data component not found.'); - } else { - logger.debug('Success: Updated data component with id ' + dataComponent.stix.id); + } + if (options.dryRun) { return res.status(200).send(dataComponent); } + logger.debug('Success: Updated data component with id ' + dataComponent.stix.id); + return res.status(200).send(dataComponent); } catch (err) { next(err); } diff --git a/app/controllers/data-sources-controller.js b/app/controllers/data-sources-controller.js index 279e1666..061bb95d 100644 --- a/app/controllers/data-sources-controller.js +++ b/app/controllers/data-sources-controller.js @@ -95,16 +95,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const dataSourceData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; - // Create the data source try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; const dataSource = await dataSourcesService.create(dataSourceData, options); + if (options.dryRun) { + return res.status(200).send(dataSource); + } logger.debug('Success: Created data source with id ' + dataSource.stix.id); return res.status(201).send(dataSource); } catch (err) { @@ -120,26 +122,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const dataSourceData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Create the data source try { const dataSource = await dataSourcesService.updateFull( req.params.stixId, req.params.modified, dataSourceData, + options, ); if (!dataSource) { return res.status(404).send('Data source not found.'); - } else { - logger.debug('Success: Updated data source with id ' + dataSource.stix.id); + } + if (options.dryRun) { return res.status(200).send(dataSource); } + logger.debug('Success: Updated data source with id ' + dataSource.stix.id); + return res.status(200).send(dataSource); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update data source. Server error.'); + return next(err); } }; diff --git a/app/controllers/detection-strategies-controller.js b/app/controllers/detection-strategies-controller.js index 88cd6d48..6f62a273 100644 --- a/app/controllers/detection-strategies-controller.js +++ b/app/controllers/detection-strategies-controller.js @@ -77,14 +77,17 @@ exports.create = async function (req, res, next) { const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the detection strategy try { const detectionStrategy = await detectionStrategiesService.create( detectionStrategyData, options, ); + if (options.dryRun) { + return res.status(200).send(detectionStrategy); + } logger.debug('Success: Created detection strategy with id ' + detectionStrategy.stix.id); return res.status(201).send(detectionStrategy); } catch (err) { @@ -93,22 +96,24 @@ exports.create = async function (req, res, next) { }; exports.updateFull = async function (req, res, next) { - // Get the data from the request const detectionStrategyData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Create the detection strategy try { const detectionStrategy = await detectionStrategiesService.updateFull( req.params.stixId, req.params.modified, detectionStrategyData, + options, ); if (!detectionStrategy) { return res.status(404).send('Detection strategy not found.'); - } else { - logger.debug('Success: Updated detection strategy with id ' + detectionStrategy.stix.id); + } + if (options.dryRun) { return res.status(200).send(detectionStrategy); } + logger.debug('Success: Updated detection strategy with id ' + detectionStrategy.stix.id); + return res.status(200).send(detectionStrategy); } catch (err) { next(err); } diff --git a/app/controllers/groups-controller.js b/app/controllers/groups-controller.js index 8aeb6339..c7b555e0 100644 --- a/app/controllers/groups-controller.js +++ b/app/controllers/groups-controller.js @@ -84,17 +84,19 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { - // Get the data from the request +exports.create = async function (req, res, next) { const groupData = req.body; const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the group try { const group = await groupsService.create(groupData, options); + if (options.dryRun) { + return res.status(200).send(group); + } logger.debug('Success: Created group with id ' + group.stix.id); return res.status(201).send(group); } catch (err) { @@ -107,28 +109,32 @@ exports.create = async function (req, res) { logger.warn('Invalid stix.type'); return res.status(400).send('Unable to create group. stix.type must be intrusion-set'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create group. Server error.'); + return next(err); } } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const groupData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { - // Create the group - const group = await groupsService.updateFull(req.params.stixId, req.params.modified, groupData); + const group = await groupsService.updateFull( + req.params.stixId, + req.params.modified, + groupData, + options, + ); if (!group) { return res.status(404).send('Group not found.'); - } else { - logger.debug('Success: Updated group with id ' + group.stix.id); + } + if (options.dryRun) { return res.status(200).send(group); } + logger.debug('Success: Updated group with id ' + group.stix.id); + return res.status(200).send(group); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update group. Server error.'); + return next(err); } }; diff --git a/app/controllers/identities-controller.js b/app/controllers/identities-controller.js index f02b4e24..90082d77 100644 --- a/app/controllers/identities-controller.js +++ b/app/controllers/identities-controller.js @@ -87,16 +87,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const identityData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; - // Create the identity try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; const identity = await identitiesService.create(identityData, options); + if (options.dryRun) { + return res.status(200).send(identity); + } logger.debug('Success: Created identity with id ' + identity.stix.id); return res.status(201).send(identity); } catch (err) { @@ -112,26 +114,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const identityData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Create the identity try { const identity = await identitiesService.updateFull( req.params.stixId, req.params.modified, identityData, + options, ); if (!identity) { return res.status(404).send('Identity not found.'); - } else { - logger.debug('Success: Updated identity with id ' + identity.stix.id); + } + if (options.dryRun) { return res.status(200).send(identity); } + logger.debug('Success: Updated identity with id ' + identity.stix.id); + return res.status(200).send(identity); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update identity. Server error.'); + return next(err); } }; diff --git a/app/controllers/marking-definitions-controller.js b/app/controllers/marking-definitions-controller.js index 9664becd..383be718 100644 --- a/app/controllers/marking-definitions-controller.js +++ b/app/controllers/marking-definitions-controller.js @@ -2,11 +2,7 @@ const markingDefinitionsService = require('../services/stix/marking-definitions-service'); const logger = require('../lib/logger'); -const { - BadlyFormattedParameterError, - CannotUpdateStaticObjectError, - InvalidQueryStringParameterError, -} = require('../exceptions'); +const { BadlyFormattedParameterError, InvalidQueryStringParameterError } = require('../exceptions'); // NOTE: A marking definition does not support the modified or revoked properties!! @@ -64,19 +60,21 @@ exports.retrieveById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const markingDefinitionData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; - // Create the marking definition try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; const markingDefinition = await markingDefinitionsService.create( markingDefinitionData, options, ); + if (options.dryRun) { + return res.status(200).send(markingDefinition); + } logger.debug('Success: Created marking definition with id ' + markingDefinition.stix.id); return res.status(201).send(markingDefinition); } catch (err) { @@ -90,30 +88,26 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const markingDefinitionData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Create the marking definition try { const markingDefinition = await markingDefinitionsService.updateFull( req.params.stixId, markingDefinitionData, + options, ); if (!markingDefinition) { return res.status(404).send('Marking definition not found.'); - } else { - logger.debug('Success: Updated marking definition with id ' + markingDefinition.stix.id); + } + if (options.dryRun) { return res.status(200).send(markingDefinition); } + logger.debug('Success: Updated marking definition with id ' + markingDefinition.stix.id); + return res.status(200).send(markingDefinition); } catch (err) { - if (err instanceof CannotUpdateStaticObjectError) { - logger.warn('Unable to update marking definition, cannot update static object'); - return res.status(400).send('Cannot update static object'); - } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update marking definition. Server error.'); - } + return next(err); } }; diff --git a/app/controllers/matrices-controller.js b/app/controllers/matrices-controller.js index 2125837e..e64c94a6 100644 --- a/app/controllers/matrices-controller.js +++ b/app/controllers/matrices-controller.js @@ -88,16 +88,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const matrixData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; - // Create the matrix try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; const matrix = await matricesService.create(matrixData, options); + if (options.dryRun) { + return res.status(200).send(matrix); + } logger.debug('Success: Created matrix with id ' + matrix.stix.id); return res.status(201).send(matrix); } catch (err) { @@ -112,27 +114,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - try { - // Get the data from the request - const matrixData = req.body; +exports.updateFull = async function (req, res, next) { + const matrixData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Update the matrix + try { const matrix = await matricesService.updateFull( req.params.stixId, req.params.modified, matrixData, + options, ); - if (!matrix) { return res.status(404).send('Matrix not found.'); } - + if (options.dryRun) { + return res.status(200).send(matrix); + } logger.debug('Success: Updated matrix with id ' + matrix.stix.id); return res.status(200).send(matrix); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update matrix. Server error.'); + return next(err); } }; diff --git a/app/controllers/mitigations-controller.js b/app/controllers/mitigations-controller.js index d5f55a2d..692b0726 100644 --- a/app/controllers/mitigations-controller.js +++ b/app/controllers/mitigations-controller.js @@ -90,16 +90,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const mitigationData = req.body; const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the mitigation try { const mitigation = await mitigationsService.create(mitigationData, options); + if (options.dryRun) { + return res.status(200).send(mitigation); + } logger.debug('Success: Created mitigation with id ' + mitigation.stix.id); return res.status(201).send(mitigation); } catch (err) { @@ -115,27 +117,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const mitigationData = req.body; - - // Create the mitigation + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { const mitigation = await mitigationsService.updateFull( req.params.stixId, req.params.modified, mitigationData, + options, ); if (!mitigation) { return res.status(404).send('Mitigation not found.'); - } else { - logger.debug('Success: Updated mitigation with id ' + mitigation.stix.id); + } + if (options.dryRun) { return res.status(200).send(mitigation); } + logger.debug('Success: Updated mitigation with id ' + mitigation.stix.id); + return res.status(200).send(mitigation); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update mitigation. Server error.'); + return next(err); } }; diff --git a/app/controllers/notes-controller.js b/app/controllers/notes-controller.js index 6660d8ae..53f5a290 100644 --- a/app/controllers/notes-controller.js +++ b/app/controllers/notes-controller.js @@ -84,16 +84,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const noteData = req.body; const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the note try { const note = await notesService.create(noteData, options); + if (options.dryRun) { + return res.status(200).send(note); + } logger.debug('Success: Created note with id ' + note.stix.id); return res.status(201).send(note); } catch (err) { @@ -109,22 +111,27 @@ exports.create = async function (req, res) { } }; -exports.updateVersion = async function (req, res) { - // Get the data from the request +exports.updateVersion = async function (req, res, next) { const noteData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Create the note try { - const note = await notesService.updateVersion(req.params.stixId, req.params.modified, noteData); + const note = await notesService.updateVersion( + req.params.stixId, + req.params.modified, + noteData, + options, + ); if (!note) { return res.status(404).send('Note not found.'); - } else { - logger.debug('Success: Updated note with id ' + note.stix.id); + } + if (options.dryRun) { return res.status(200).send(note); } + logger.debug('Success: Updated note with id ' + note.stix.id); + return res.status(200).send(note); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update note. Server error.'); + return next(err); } }; diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index 5ce19c77..f9734d0a 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -97,16 +97,18 @@ exports.retrieveVersionById = async function (req, res) { }; exports.create = async function (req, res) { - // Get the data from the request const relationshipData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, + }; - // Create the relationship try { - const options = { - import: false, - userAccountId: req.user?.userAccountId, - }; const relationship = await relationshipsService.create(relationshipData, options); + if (options.dryRun) { + return res.status(200).send(relationship); + } logger.debug('Success: Created relationship with id ' + relationship.stix.id); return res.status(201).send(relationship); } catch (err) { @@ -122,26 +124,27 @@ exports.create = async function (req, res) { } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const relationshipData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; - // Create the relationship try { const relationship = await relationshipsService.updateFull( req.params.stixId, req.params.modified, relationshipData, + options, ); if (!relationship) { return res.status(404).send('Relationship not found.'); - } else { - logger.debug('Success: Updated relationship with id ' + relationship.stix.id); + } + if (options.dryRun) { return res.status(200).send(relationship); } + logger.debug('Success: Updated relationship with id ' + relationship.stix.id); + return res.status(200).send(relationship); } catch (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update relationship. Server error.'); + return next(err); } }; diff --git a/app/controllers/software-controller.js b/app/controllers/software-controller.js index 79b3c129..f6a20d31 100644 --- a/app/controllers/software-controller.js +++ b/app/controllers/software-controller.js @@ -94,18 +94,19 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { - // Get the data from the request +exports.create = async function (req, res, next) { const softwareData = req.body; - const options = { import: false, userAccountId: req.user?.userAccountId, + dryRun: req.query.dryRun === 'true' || req.query.dryRun === true, }; - // Create the software try { - const software = await await softwareService.create(softwareData, options); + const software = await softwareService.create(softwareData, options); + if (options.dryRun) { + return res.status(200).send(software); + } logger.debug('Success: Created software with id ' + software.stix.id); return res.status(201).send(software); } catch (err) { @@ -120,37 +121,32 @@ exports.create = async function (req, res) { .status(400) .send(`Unable to create software, property ${err.propertyName} is not allowed`); } else { - console.log('create error'); - console.log(err); - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create software. Server error.'); + return next(err); } } }; -exports.updateFull = async function (req, res) { - // Get the data from the request +exports.updateFull = async function (req, res, next) { const softwareData = req.body; + const options = { dryRun: req.query.dryRun === 'true' || req.query.dryRun === true }; try { - // Create the software const software = await softwareService.updateFull( req.params.stixId, req.params.modified, softwareData, + options, ); - if (!software) { return res.status(404).send('Software not found.'); - } else { - logger.debug('Success: Updated software with id ' + software.stix.id); + } + if (options.dryRun) { return res.status(200).send(software); } + logger.debug('Success: Updated software with id ' + software.stix.id); + return res.status(200).send(software); } catch (err) { - console.log('update full error'); - console.log(err); - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to update software. Server error.'); + return next(err); } }; From 062db925a1de6774d0612546da475190ae2dd99b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 12 Mar 2026 11:44:42 -0400 Subject: [PATCH 220/370] fix: update controllers to enable fallback to global error handler --- app/controllers/assets-controller.js | 5 ++--- app/controllers/campaigns-controller.js | 5 ++--- app/controllers/data-sources-controller.js | 5 ++--- app/controllers/identities-controller.js | 5 ++--- app/controllers/marking-definitions-controller.js | 5 ++--- app/controllers/matrices-controller.js | 5 ++--- app/controllers/mitigations-controller.js | 5 ++--- app/controllers/notes-controller.js | 5 ++--- app/controllers/relationships-controller.js | 5 ++--- 9 files changed, 18 insertions(+), 27 deletions(-) diff --git a/app/controllers/assets-controller.js b/app/controllers/assets-controller.js index 869b3537..2027e4be 100644 --- a/app/controllers/assets-controller.js +++ b/app/controllers/assets-controller.js @@ -92,7 +92,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const assetData = req.body; const options = { import: false, @@ -114,8 +114,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create asset. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create asset. Server error.'); + return next(err); } } }; diff --git a/app/controllers/campaigns-controller.js b/app/controllers/campaigns-controller.js index 24e7ff9f..df769949 100644 --- a/app/controllers/campaigns-controller.js +++ b/app/controllers/campaigns-controller.js @@ -88,7 +88,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const campaignData = req.body; const options = { import: false, @@ -113,8 +113,7 @@ exports.create = async function (req, res) { logger.warn('Invalid stix.type'); return res.status(400).send('Unable to create campaign. stix.type must be campaign'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create campaign. Server error.'); + return next(err); } } }; diff --git a/app/controllers/data-sources-controller.js b/app/controllers/data-sources-controller.js index 061bb95d..5106c2b4 100644 --- a/app/controllers/data-sources-controller.js +++ b/app/controllers/data-sources-controller.js @@ -94,7 +94,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const dataSourceData = req.body; const options = { import: false, @@ -116,8 +116,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create data source. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create data source. Server error.'); + return next(err); } } }; diff --git a/app/controllers/identities-controller.js b/app/controllers/identities-controller.js index 90082d77..ce9be48f 100644 --- a/app/controllers/identities-controller.js +++ b/app/controllers/identities-controller.js @@ -86,7 +86,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const identityData = req.body; const options = { import: false, @@ -108,8 +108,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create identity. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create identity. Server error.'); + return next(err); } } }; diff --git a/app/controllers/marking-definitions-controller.js b/app/controllers/marking-definitions-controller.js index 383be718..715183e9 100644 --- a/app/controllers/marking-definitions-controller.js +++ b/app/controllers/marking-definitions-controller.js @@ -59,7 +59,7 @@ exports.retrieveById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const markingDefinitionData = req.body; const options = { import: false, @@ -82,8 +82,7 @@ exports.create = async function (req, res) { logger.warn('Unable to create marking definition: Stix id not allowed'); return res.status(400).send('Stix id not allowed.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create marking definition. Server error.'); + return next(err); } } }; diff --git a/app/controllers/matrices-controller.js b/app/controllers/matrices-controller.js index e64c94a6..807d1988 100644 --- a/app/controllers/matrices-controller.js +++ b/app/controllers/matrices-controller.js @@ -87,7 +87,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const matrixData = req.body; const options = { import: false, @@ -109,8 +109,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create matrix. Duplicate stix.id and stix.modified properties.'); } - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create matrix. Server error.'); + return next(err); } }; diff --git a/app/controllers/mitigations-controller.js b/app/controllers/mitigations-controller.js index 692b0726..65af41ee 100644 --- a/app/controllers/mitigations-controller.js +++ b/app/controllers/mitigations-controller.js @@ -89,7 +89,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const mitigationData = req.body; const options = { import: false, @@ -111,8 +111,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create mitigation. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create mitigation. Server error.'); + return next(err); } } }; diff --git a/app/controllers/notes-controller.js b/app/controllers/notes-controller.js index 53f5a290..669e022a 100644 --- a/app/controllers/notes-controller.js +++ b/app/controllers/notes-controller.js @@ -83,7 +83,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const noteData = req.body; const options = { import: false, @@ -105,8 +105,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create note. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create note. Server error.'); + return next(err); } } }; diff --git a/app/controllers/relationships-controller.js b/app/controllers/relationships-controller.js index f9734d0a..972d75ee 100644 --- a/app/controllers/relationships-controller.js +++ b/app/controllers/relationships-controller.js @@ -96,7 +96,7 @@ exports.retrieveVersionById = async function (req, res) { } }; -exports.create = async function (req, res) { +exports.create = async function (req, res, next) { const relationshipData = req.body; const options = { import: false, @@ -118,8 +118,7 @@ exports.create = async function (req, res) { .status(409) .send('Unable to create relationship. Duplicate stix.id and stix.modified properties.'); } else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to create relationship. Server error.'); + return next(err); } } }; From 9b4cf828531e4029861177cf54da2070ccaafc09 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 12 Mar 2026 12:47:13 -0400 Subject: [PATCH 221/370] refactor: add logging statements to ext ref builder --- app/lib/external-reference-builder.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/lib/external-reference-builder.js b/app/lib/external-reference-builder.js index f59798a0..0aabda94 100644 --- a/app/lib/external-reference-builder.js +++ b/app/lib/external-reference-builder.js @@ -1,5 +1,7 @@ 'use strict'; +const logger = require('./logger'); + /** * Map STIX types to their corresponding ATT&CK website URL paths */ @@ -28,7 +30,8 @@ const STIX_TYPE_TO_URL_PATH = { * @returns {object} External reference object with source_name, external_id, and url */ function buildAttackExternalReference(attackId, stixType, options = {}) { - if (!attackId || !stixType) { + if (!attackId && !stixType) { + logger.warn('buildAttackExternalReference called with no attackId or stixType'); return null; } @@ -52,6 +55,9 @@ function buildAttackExternalReference(attackId, stixType, options = {}) { const urlPath = STIX_TYPE_TO_URL_PATH[stixType]; if (!urlPath) { // Type doesn't have a URL mapping, return reference without URL + logger.debug( + `No URL path mapping for STIX type '${stixType}'; omitting url from external reference`, + ); return { source_name: 'mitre-attack', external_id: attackId, @@ -118,7 +124,8 @@ function createAttackExternalReference(data) { const attackId = data.workspace?.attack_id; const stixType = data.stix?.type; - if (!attackId || !stixType) { + if (!attackId && !stixType) { + logger.warn('createAttackExternalReference called with no attackId or stixType'); return null; } From 8e488fc889ed117ec59b2e4dc629f8f48b994f5a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:37:56 -0400 Subject: [PATCH 222/370] fix: resolve bug where techniqueMatchesTactic throws when x_mitre_domains is undefined There was a bug in the retrieveTechniquesForMatrix endpoint that occurs when the matrix references a tactic that is not in a domain (i.e., the tactic's x_mitre_domains field is undefined. This would result in a error saying that we cannot read the map property from undefined. Added a guard to ensure that if no domain exists, no techniques can match, so the filter function returns false. Also added a ? on the kill_chain_phases.some call since a technique with no kill chain phases would have the same problem. --- app/services/stix/tactics-service.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/services/stix/tactics-service.js b/app/services/stix/tactics-service.js index 296db4af..70a12d45 100644 --- a/app/services/stix/tactics-service.js +++ b/app/services/stix/tactics-service.js @@ -16,10 +16,13 @@ class TacticsService extends BaseService { // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) // 2. The phase's phase_name matches the tactic's x_mitre_shortname // Convert the tactic's domain names to kill chain names + if (!tactic.stix.x_mitre_domains?.length) { + return false; + } const tacticKillChainNames = tactic.stix.x_mitre_domains.map( (domain) => config.domainToKillChainMap[domain], ); - return technique.stix.kill_chain_phases.some( + return technique.stix.kill_chain_phases?.some( (phase) => phase.phase_name === tactic.stix.x_mitre_shortname && tacticKillChainNames.includes(phase.kill_chain_name), From 6d05da5c08c82739ea620a03492332158d9f2014 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:07:26 -0400 Subject: [PATCH 223/370] fix: impl workflow to auto change technique phase names when tactic renamed Techniques are associated with Tactics through their kill_chain_phases..phase_name field. Previously, when a tactic was renamed, any connected technique was not automatically updated to reflect the new name. This would break several frontend views and put the database into a broken/inconsistent state. We've introduced a new, automated workflow to fix this. When the tactic name changes, a new event, TACTIC_SHORTNAME_CHANGED, will fire, notifying the techniques service that some technique phase names may need to be updated. The worfklow supports both in-place operations (via the PUT endpoint) and new-doc operations (via the POST endpoint). --- app/lib/event-constants.js | 3 + app/repository/techniques-repository.js | 47 ++++++++- app/services/stix/tactics-service.js | 102 +++++++++++++++++++ app/services/stix/techniques-service.js | 124 ++++++++++++++++++++++++ 4 files changed, 275 insertions(+), 1 deletion(-) diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index 2b37004c..5d4d8f39 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -130,6 +130,9 @@ module.exports = Object.freeze({ // Data Source - Data Component relationship DATA_SOURCE_COMPONENTS_CHANGED: 'x-mitre-data-source::components-changed', + // Tactic - shortname change (phase_name in techniques) + TACTIC_SHORTNAME_CHANGED: 'x-mitre-tactic::shortname-changed', + // Matrix - Tactics relationship MATRIX_TACTICS_CHANGED: 'x-mitre-matrix::tactics-changed', diff --git a/app/repository/techniques-repository.js b/app/repository/techniques-repository.js index 86ec081d..ef705914 100644 --- a/app/repository/techniques-repository.js +++ b/app/repository/techniques-repository.js @@ -2,7 +2,52 @@ const BaseRepository = require('./_base.repository'); const Technique = require('../models/technique-model'); +const { DatabaseError } = require('../exceptions'); -class TechniqueRepository extends BaseRepository {} +class TechniqueRepository extends BaseRepository { + /** + * Retrieve the latest version of each technique whose kill_chain_phases contains + * a phase with the given phase_name. Returns plain objects (no _id or internal fields). + * Used when creating new technique versions to propagate a tactic shortname change. + * + * @param {string} phaseName - The phase_name value to filter by + * @returns {Promise>} Array of plain technique objects (latest version each) + */ + async retrieveAllLatestByPhaseName(phaseName) { + try { + const aggregation = [ + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { $match: { 'stix.kill_chain_phases.phase_name': phaseName } }, + { $project: { _id: 0, __v: 0, __t: 0 } }, + ]; + return await this.model.aggregate(aggregation).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + /** + * Bulk-update the phase_name in kill_chain_phases across all technique versions. + * Called when a tactic's x_mitre_shortname changes so that connected techniques + * remain consistent. + * + * @param {string} oldPhaseName - The previous x_mitre_shortname value + * @param {string} newPhaseName - The new x_mitre_shortname value + * @returns {Promise} + */ + async updatePhaseName(oldPhaseName, newPhaseName) { + try { + return await this.model.updateMany( + { 'stix.kill_chain_phases.phase_name': oldPhaseName }, + { $set: { 'stix.kill_chain_phases.$[elem].phase_name': newPhaseName } }, + { arrayFilters: [{ 'elem.phase_name': oldPhaseName }] }, + ); + } catch (err) { + throw new DatabaseError(err); + } + } +} module.exports = new TechniqueRepository(Technique); diff --git a/app/services/stix/tactics-service.js b/app/services/stix/tactics-service.js index 70a12d45..b9c1a4e9 100644 --- a/app/services/stix/tactics-service.js +++ b/app/services/stix/tactics-service.js @@ -6,7 +6,21 @@ const tacticsRepository = require('../../repository/tactics-repository'); const { Tactic: TacticType } = require('../../lib/types'); const techniquesService = require('./techniques-service'); const { BadlyFormattedParameterError, MissingParameterError } = require('../../exceptions'); +const EventBus = require('../../lib/event-bus'); +const EventConstants = require('../../lib/event-constants'); +const logger = require('../../lib/logger'); +/** + * Service for managing tactics + * + * Lifecycle hooks: + * - beforeUpdate: Detects changes to x_mitre_shortname and stores old/new values + * - afterUpdate: Emits domain event when shortname changes so TechniquesService can + * update kill_chain_phases.phase_name on all connected techniques + * + * Events emitted (listened to by TechniquesService): + * - x-mitre-tactic::shortname-changed + */ class TacticsService extends BaseService { static techniquesService = null; @@ -30,6 +44,94 @@ class TacticsService extends BaseService { }; } + /** + * Detect shortname changes when creating a new version of an existing tactic. + * Compares against the current latest version; stores the change so afterCreate + * can emit the domain event. + */ + // eslint-disable-next-line no-unused-vars + async beforeCreate(data, options) { + if (!data.stix?.id) { + return; // Brand-new tactic — no previous version to compare against + } + + try { + const previousVersion = await tacticsRepository.retrieveLatestByStixId(data.stix.id); + if (!previousVersion) return; + + const oldShortname = previousVersion.stix?.x_mitre_shortname; + const newShortname = data.stix?.x_mitre_shortname; + + if (oldShortname && newShortname && oldShortname !== newShortname) { + this._shortnameChangeViaCreate = { oldShortname, newShortname }; + } + } catch { + logger.debug(`TacticsService: No previous version found for tactic ${data.stix.id}`); + } + } + + /** + * Emit a domain event when a new tactic version has a changed x_mitre_shortname. + * TechniquesService will create new technique versions to propagate the change. + */ + // eslint-disable-next-line no-unused-vars + async afterCreate(document, options) { + if (this._shortnameChangeViaCreate) { + const { oldShortname, newShortname } = this._shortnameChangeViaCreate; + + logger.info( + `TacticsService: New tactic version with x_mitre_shortname change '${oldShortname}' -> '${newShortname}', emitting event`, + { tacticId: document.stix.id }, + ); + + await EventBus.emit(EventConstants.TACTIC_SHORTNAME_CHANGED, { + tacticId: document.stix.id, + oldShortname, + newShortname, + createNewVersion: true, + }); + + delete this._shortnameChangeViaCreate; + } + } + + /** + * Detect changes to x_mitre_shortname before the update is persisted. + * Stores the old/new values so afterUpdate can emit the domain event. + */ + // eslint-disable-next-line no-unused-vars + async beforeUpdate(stixId, stixModified, data, existingDocument, options) { + const oldShortname = existingDocument.stix?.x_mitre_shortname; + const newShortname = data.stix?.x_mitre_shortname; + + if (oldShortname && newShortname && oldShortname !== newShortname) { + this._shortnameChange = { oldShortname, newShortname }; + } + } + + /** + * Emit a domain event when x_mitre_shortname changed so TechniquesService can + * update kill_chain_phases.phase_name on all connected techniques. + */ + async afterUpdate(updatedDocument) { + if (this._shortnameChange) { + const { oldShortname, newShortname } = this._shortnameChange; + + logger.info( + `TacticsService: x_mitre_shortname changed '${oldShortname}' -> '${newShortname}', emitting event`, + { tacticId: updatedDocument.stix.id }, + ); + + await EventBus.emit(EventConstants.TACTIC_SHORTNAME_CHANGED, { + tacticId: updatedDocument.stix.id, + oldShortname, + newShortname, + }); + + delete this._shortnameChange; + } + } + static getPageOfData(data, options) { const startPos = options.offset; const endPos = diff --git a/app/services/stix/techniques-service.js b/app/services/stix/techniques-service.js index 023f8885..327db2b6 100644 --- a/app/services/stix/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -6,8 +6,130 @@ const techniquesRepository = require('../../repository/techniques-repository'); const { Technique: TechniqueType } = require('../../lib/types'); const { BadlyFormattedParameterError, MissingParameterError } = require('../../exceptions'); +const EventBus = require('../../lib/event-bus'); +const EventConstants = require('../../lib/event-constants'); +const logger = require('../../lib/logger'); +/** + * Service for managing techniques and sub-techniques + * + * Event listeners: + * - x-mitre-tactic::shortname-changed - Updates kill_chain_phases.phase_name on all + * technique documents when a tactic's x_mitre_shortname changes + */ class TechniquesService extends BaseService { + /** + * Initialize event listeners. + * Called once on module load. + */ + static initializeEventListeners() { + EventBus.on( + EventConstants.TACTIC_SHORTNAME_CHANGED, + TechniquesService.handleTacticShortnameChanged.bind(TechniquesService), + ); + + logger.info('TechniquesService: Event listeners initialized'); + } + + /** + * Handle a tactic x_mitre_shortname change by updating all technique documents + * whose kill_chain_phases contain the old phase_name. + * + * @param {Object} payload - Event payload + * @param {string} payload.tacticId - STIX ID of the updated tactic + * @param {string} payload.oldShortname - Previous x_mitre_shortname value + * @param {string} payload.newShortname - New x_mitre_shortname value + */ + static async handleTacticShortnameChanged(payload) { + const { tacticId, oldShortname, newShortname, createNewVersion } = payload; + + logger.info( + `TechniquesService: Propagating tactic shortname change '${oldShortname}' -> '${newShortname}' via ${createNewVersion ? 'new technique versions' : 'in-place update'}`, + { tacticId }, + ); + + if (createNewVersion) { + await TechniquesService._propagateShortnameViaNewVersions( + tacticId, + oldShortname, + newShortname, + ); + } else { + await TechniquesService._propagateShortnameInPlace(tacticId, oldShortname, newShortname); + } + } + + /** + * Propagate a shortname change by creating a new version of each connected technique. + * Used when the tactic shortname changed via a create (POST) operation, keeping the + * technique history intact by appending rather than editing in-place. + * + * @param {string} tacticId - STIX ID of the updated tactic (for logging) + * @param {string} oldShortname - Previous x_mitre_shortname value + * @param {string} newShortname - New x_mitre_shortname value + */ + static async _propagateShortnameViaNewVersions(tacticId, oldShortname, newShortname) { + const techniques = await techniquesRepository.retrieveAllLatestByPhaseName(oldShortname); + + logger.info( + `TechniquesService: Creating new versions for ${techniques.length} technique(s) due to tactic shortname change`, + { tacticId, oldShortname, newShortname }, + ); + + for (const technique of techniques) { + try { + // Clone stix shallowly — only kill_chain_phases needs to change + const newVersion = { + ...technique, + stix: { + ...technique.stix, + modified: new Date().toISOString(), + kill_chain_phases: (technique.stix.kill_chain_phases || []).map((phase) => + phase.phase_name === oldShortname + ? { ...phase, phase_name: newShortname } + : { ...phase }, + ), + }, + }; + + await techniquesRepository.save(newVersion); + + logger.info( + `TechniquesService: Created new version of technique ${technique.stix.id} with updated phase_name`, + { tacticId, oldShortname, newShortname }, + ); + } catch (error) { + logger.error( + `TechniquesService: Error creating new version of technique ${technique.stix?.id}:`, + error, + ); + } + } + } + + /** + * Propagate a shortname change by updating all technique documents in-place. + * Used when the tactic shortname changed via an update (PUT) operation. + * + * @param {string} tacticId - STIX ID of the updated tactic (for logging) + * @param {string} oldShortname - Previous x_mitre_shortname value + * @param {string} newShortname - New x_mitre_shortname value + */ + static async _propagateShortnameInPlace(tacticId, oldShortname, newShortname) { + try { + const result = await techniquesRepository.updatePhaseName(oldShortname, newShortname); + logger.info( + `TechniquesService: Updated ${result.modifiedCount} technique document(s) in-place for tactic shortname change`, + { tacticId, oldShortname, newShortname }, + ); + } catch (error) { + logger.error( + `TechniquesService: Error updating techniques in-place for tactic shortname change '${oldShortname}' -> '${newShortname}':`, + error, + ); + } + } + static tacticsService = null; static tacticMatchesTechnique(technique) { @@ -87,4 +209,6 @@ class TechniquesService extends BaseService { } } +TechniquesService.initializeEventListeners(); + module.exports = new TechniquesService(TechniqueType, techniquesRepository); From a4630c359cee1cf986140889c8cfc04eaed86902 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:08:41 -0400 Subject: [PATCH 224/370] feat: implement revoke endpoints on all controllers --- app/controllers/assets-controller.js | 13 +++++++++++++ app/controllers/campaigns-controller.js | 13 +++++++++++++ app/controllers/data-components-controller.js | 13 +++++++++++++ app/controllers/data-sources-controller.js | 13 +++++++++++++ app/controllers/groups-controller.js | 13 +++++++++++++ app/controllers/matrices-controller.js | 13 +++++++++++++ app/controllers/mitigations-controller.js | 13 +++++++++++++ app/controllers/software-controller.js | 13 +++++++++++++ app/controllers/tactics-controller.js | 13 +++++++++++++ app/controllers/techniques-controller.js | 13 +++++++++++++ 10 files changed, 130 insertions(+) diff --git a/app/controllers/assets-controller.js b/app/controllers/assets-controller.js index 2027e4be..808c5c7f 100644 --- a/app/controllers/assets-controller.js +++ b/app/controllers/assets-controller.js @@ -173,3 +173,16 @@ exports.deleteVersionById = async function (req, res) { return res.status(500).send('Unable to delete asset. Server error.'); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await assetsService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/campaigns-controller.js b/app/controllers/campaigns-controller.js index df769949..6f7fc6ed 100644 --- a/app/controllers/campaigns-controller.js +++ b/app/controllers/campaigns-controller.js @@ -174,3 +174,16 @@ exports.deleteById = async function (req, res) { return res.status(500).send('Unable to delete campaign. Server error.'); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await campaignsService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/data-components-controller.js b/app/controllers/data-components-controller.js index 9a89f23d..f61accb5 100644 --- a/app/controllers/data-components-controller.js +++ b/app/controllers/data-components-controller.js @@ -177,3 +177,16 @@ exports.deleteById = async function (req, res, next) { next(err); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await dataComponentsService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/data-sources-controller.js b/app/controllers/data-sources-controller.js index 5106c2b4..d6a3b0c4 100644 --- a/app/controllers/data-sources-controller.js +++ b/app/controllers/data-sources-controller.js @@ -177,3 +177,16 @@ exports.deleteById = async function (req, res) { return res.status(500).send('Unable to delete data source. Server error.'); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await dataSourcesService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/groups-controller.js b/app/controllers/groups-controller.js index c7b555e0..1ebd12fe 100644 --- a/app/controllers/groups-controller.js +++ b/app/controllers/groups-controller.js @@ -167,3 +167,16 @@ exports.deleteById = async function (req, res) { return res.status(500).send('Unable to delete group. Server error.'); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await groupsService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/matrices-controller.js b/app/controllers/matrices-controller.js index 807d1988..fe410f30 100644 --- a/app/controllers/matrices-controller.js +++ b/app/controllers/matrices-controller.js @@ -168,6 +168,19 @@ exports.deleteById = async function (req, res) { } }; +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await matricesService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; + exports.retrieveTechniquesForMatrix = async function (req, res) { try { const techniquesByTactic = await matricesService.retrieveTechniquesForMatrix( diff --git a/app/controllers/mitigations-controller.js b/app/controllers/mitigations-controller.js index 65af41ee..76eae2d3 100644 --- a/app/controllers/mitigations-controller.js +++ b/app/controllers/mitigations-controller.js @@ -172,3 +172,16 @@ exports.deleteById = async function (req, res) { return res.status(500).send('Unable to delete mitigation. Server error.'); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await mitigationsService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/software-controller.js b/app/controllers/software-controller.js index f6a20d31..a0a27ccb 100644 --- a/app/controllers/software-controller.js +++ b/app/controllers/software-controller.js @@ -188,3 +188,16 @@ exports.deleteById = async function (req, res) { return res.status(500).send('Unable to delete software. Server error.'); } }; + +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await softwareService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; diff --git a/app/controllers/tactics-controller.js b/app/controllers/tactics-controller.js index cd5720c9..078e5409 100644 --- a/app/controllers/tactics-controller.js +++ b/app/controllers/tactics-controller.js @@ -176,6 +176,19 @@ exports.deleteById = async function (req, res) { } }; +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await tacticsService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; + exports.retrieveTechniquesForTactic = async function (req, res) { try { const options = { diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index 9f1493bb..6e3fd602 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -176,6 +176,19 @@ exports.deleteById = async function (req, res) { } }; +exports.revoke = async function (req, res, next) { + try { + const options = { + preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + userAccountId: req.user?.userAccountId, + }; + const result = await techniquesService.revoke(req.params.stixId, req.body, options); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; + exports.retrieveTacticsForTechnique = async function (req, res) { try { const options = { From 00342f732bf8d6a54dcd051381e4b57abe5e35f8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:09:50 -0400 Subject: [PATCH 225/370] feat: bind new revoke endpoints to all routers --- app/routes/assets-routes.js | 4 ++++ app/routes/campaigns-routes.js | 4 ++++ app/routes/data-components-routes.js | 8 ++++++++ app/routes/data-sources-routes.js | 4 ++++ app/routes/groups-routes.js | 4 ++++ app/routes/matrices-routes.js | 4 ++++ app/routes/mitigations-routes.js | 4 ++++ app/routes/software-routes.js | 4 ++++ app/routes/tactics-routes.js | 4 ++++ app/routes/techniques-routes.js | 4 ++++ 10 files changed, 44 insertions(+) diff --git a/app/routes/assets-routes.js b/app/routes/assets-routes.js index bc434fe7..7463c439 100644 --- a/app/routes/assets-routes.js +++ b/app/routes/assets-routes.js @@ -36,4 +36,8 @@ router .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), assetsController.deleteVersionById); +router + .route('/assets/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), assetsController.revoke); + module.exports = router; diff --git a/app/routes/campaigns-routes.js b/app/routes/campaigns-routes.js index efdb09fb..97ba468e 100644 --- a/app/routes/campaigns-routes.js +++ b/app/routes/campaigns-routes.js @@ -40,4 +40,8 @@ router campaignsController.deleteVersionById, ); +router + .route('/campaigns/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), campaignsController.revoke); + module.exports = router; diff --git a/app/routes/data-components-routes.js b/app/routes/data-components-routes.js index 3dbc326f..5f42e662 100644 --- a/app/routes/data-components-routes.js +++ b/app/routes/data-components-routes.js @@ -64,4 +64,12 @@ router dataComponentsController.deleteVersionById, ); +router + .route('/data-components/:stixId/revoke') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + dataComponentsController.revoke, + ); + module.exports = router; diff --git a/app/routes/data-sources-routes.js b/app/routes/data-sources-routes.js index f220850f..9e51d42a 100644 --- a/app/routes/data-sources-routes.js +++ b/app/routes/data-sources-routes.js @@ -44,4 +44,8 @@ router dataSourcesController.deleteVersionById, ); +router + .route('/data-sources/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), dataSourcesController.revoke); + module.exports = router; diff --git a/app/routes/groups-routes.js b/app/routes/groups-routes.js index 41b0ad9d..709e2490 100644 --- a/app/routes/groups-routes.js +++ b/app/routes/groups-routes.js @@ -36,4 +36,8 @@ router .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), groupsController.deleteVersionById); +router + .route('/groups/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), groupsController.revoke); + module.exports = router; diff --git a/app/routes/matrices-routes.js b/app/routes/matrices-routes.js index 20a27c32..8b78d78c 100644 --- a/app/routes/matrices-routes.js +++ b/app/routes/matrices-routes.js @@ -36,6 +36,10 @@ router .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), matricesController.deleteVersionById); +router + .route('/matrices/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), matricesController.revoke); + router .route('/matrices/:stixId/modified/:modified/techniques') .get( diff --git a/app/routes/mitigations-routes.js b/app/routes/mitigations-routes.js index 04b7e234..2f062f28 100644 --- a/app/routes/mitigations-routes.js +++ b/app/routes/mitigations-routes.js @@ -44,4 +44,8 @@ router mitigationsController.deleteVersionById, ); +router + .route('/mitigations/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), mitigationsController.revoke); + module.exports = router; diff --git a/app/routes/software-routes.js b/app/routes/software-routes.js index c1707464..3579936c 100644 --- a/app/routes/software-routes.js +++ b/app/routes/software-routes.js @@ -36,4 +36,8 @@ router .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), softwareController.deleteVersionById); +router + .route('/software/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), softwareController.revoke); + module.exports = router; diff --git a/app/routes/tactics-routes.js b/app/routes/tactics-routes.js index 0e7b6080..56e28640 100644 --- a/app/routes/tactics-routes.js +++ b/app/routes/tactics-routes.js @@ -36,6 +36,10 @@ router .put(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.updateFull) .delete(authn.authenticate, authz.requireRole(authz.admin), tacticsController.deleteVersionById); +router + .route('/tactics/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), tacticsController.revoke); + router .route('/tactics/:stixId/modified/:modified/techniques') .get( diff --git a/app/routes/techniques-routes.js b/app/routes/techniques-routes.js index c9908aac..63537881 100644 --- a/app/routes/techniques-routes.js +++ b/app/routes/techniques-routes.js @@ -40,6 +40,10 @@ router techniquesController.deleteVersionById, ); +router + .route('/techniques/:stixId/revoke') + .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.revoke); + router .route('/techniques/:stixId/modified/:modified/tactics') .get( From 8a956990678a6327940ccbef473ae2dac1aa01b4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:10:27 -0400 Subject: [PATCH 226/370] style: apply formatting --- app/controllers/assets-controller.js | 3 ++- app/controllers/campaigns-controller.js | 3 ++- app/controllers/data-components-controller.js | 3 ++- app/controllers/data-sources-controller.js | 3 ++- app/controllers/groups-controller.js | 3 ++- app/controllers/matrices-controller.js | 3 ++- app/controllers/mitigations-controller.js | 3 ++- app/controllers/software-controller.js | 3 ++- app/controllers/tactics-controller.js | 3 ++- app/controllers/techniques-controller.js | 3 ++- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/app/controllers/assets-controller.js b/app/controllers/assets-controller.js index 808c5c7f..1de1f880 100644 --- a/app/controllers/assets-controller.js +++ b/app/controllers/assets-controller.js @@ -177,7 +177,8 @@ exports.deleteVersionById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await assetsService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/campaigns-controller.js b/app/controllers/campaigns-controller.js index 6f7fc6ed..ebcfd8b3 100644 --- a/app/controllers/campaigns-controller.js +++ b/app/controllers/campaigns-controller.js @@ -178,7 +178,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await campaignsService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/data-components-controller.js b/app/controllers/data-components-controller.js index f61accb5..98c880d0 100644 --- a/app/controllers/data-components-controller.js +++ b/app/controllers/data-components-controller.js @@ -181,7 +181,8 @@ exports.deleteById = async function (req, res, next) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await dataComponentsService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/data-sources-controller.js b/app/controllers/data-sources-controller.js index d6a3b0c4..d7933d55 100644 --- a/app/controllers/data-sources-controller.js +++ b/app/controllers/data-sources-controller.js @@ -181,7 +181,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await dataSourcesService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/groups-controller.js b/app/controllers/groups-controller.js index 1ebd12fe..eeff37e3 100644 --- a/app/controllers/groups-controller.js +++ b/app/controllers/groups-controller.js @@ -171,7 +171,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await groupsService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/matrices-controller.js b/app/controllers/matrices-controller.js index fe410f30..7e0e7738 100644 --- a/app/controllers/matrices-controller.js +++ b/app/controllers/matrices-controller.js @@ -171,7 +171,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await matricesService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/mitigations-controller.js b/app/controllers/mitigations-controller.js index 76eae2d3..ddaec0cf 100644 --- a/app/controllers/mitigations-controller.js +++ b/app/controllers/mitigations-controller.js @@ -176,7 +176,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await mitigationsService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/software-controller.js b/app/controllers/software-controller.js index a0a27ccb..4f1f7717 100644 --- a/app/controllers/software-controller.js +++ b/app/controllers/software-controller.js @@ -192,7 +192,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await softwareService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/tactics-controller.js b/app/controllers/tactics-controller.js index 078e5409..4c7cdfd2 100644 --- a/app/controllers/tactics-controller.js +++ b/app/controllers/tactics-controller.js @@ -179,7 +179,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await tacticsService.revoke(req.params.stixId, req.body, options); diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index 6e3fd602..7f39fef5 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -179,7 +179,8 @@ exports.deleteById = async function (req, res) { exports.revoke = async function (req, res, next) { try { const options = { - preserveRelationships: req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, + preserveRelationships: + req.query.preserveRelationships === 'true' || req.query.preserveRelationships === true, userAccountId: req.user?.userAccountId, }; const result = await techniquesService.revoke(req.params.stixId, req.body, options); From ed3c13c0fb1fe536d614f141e0a2de3292d423bb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:11:42 -0400 Subject: [PATCH 227/370] feat: implement new exception types for revoke workflow --- app/exceptions/index.js | 16 ++++++++++++++++ app/lib/error-handler.js | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/app/exceptions/index.js b/app/exceptions/index.js index 9f7eef7f..804d45b1 100644 --- a/app/exceptions/index.js +++ b/app/exceptions/index.js @@ -204,6 +204,18 @@ class SchemaValidationError extends CustomError { } } +class AlreadyRevokedError extends CustomError { + constructor(options) { + super('Object has already been revoked', options); + } +} + +class SelfRevocationError extends CustomError { + constructor(options) { + super('An object cannot revoke itself', options); + } +} + class AlreadyReleasedError extends CustomError { constructor(version, options) { super(`This snapshot has already been tagged as version ${version}`, options); @@ -260,6 +272,10 @@ module.exports = { ValidationError, SchemaValidationError, + //** Revocation errors */ + AlreadyRevokedError, + SelfRevocationError, + //** Version control errors */ AlreadyReleasedError, InvalidVersionError, diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index 1267156d..b805dd36 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -31,6 +31,8 @@ const { InvalidPostOperationError, ValidationError, DefaultMarkingDefinitionsNotFoundError, + AlreadyRevokedError, + SelfRevocationError, AlreadyReleasedError, InvalidVersionError, ReleaseConflictError, @@ -93,6 +95,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof InvalidTypeError || err instanceof PropertyNotAllowedError || err instanceof CannotUpdateStaticObjectError || + err instanceof SelfRevocationError || err instanceof BadRequestError || err instanceof ValidationError || err instanceof InvalidVersionError || @@ -121,6 +124,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof DuplicateIdError || err instanceof DuplicateEmailError || err instanceof DuplicateNameError || + err instanceof AlreadyRevokedError || err instanceof AlreadyReleasedError || err instanceof ReleaseConflictError ) { From 9a263ca5a556c707429a55c0cbdcd9d677e4be93 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:19:09 -0400 Subject: [PATCH 228/370] feat: define new revoke endpoints in openapi spec files --- app/api/definitions/openapi.yml | 30 +++++++++++ app/api/definitions/paths/assets-paths.yml | 53 +++++++++++++++++++ app/api/definitions/paths/campaigns-paths.yml | 53 +++++++++++++++++++ .../paths/data-components-paths.yml | 53 +++++++++++++++++++ .../definitions/paths/data-sources-paths.yml | 53 +++++++++++++++++++ app/api/definitions/paths/groups-paths.yml | 53 +++++++++++++++++++ app/api/definitions/paths/matrices-paths.yml | 53 +++++++++++++++++++ .../definitions/paths/mitigations-paths.yml | 53 +++++++++++++++++++ app/api/definitions/paths/software-paths.yml | 53 +++++++++++++++++++ app/api/definitions/paths/tactics-paths.yml | 53 +++++++++++++++++++ .../definitions/paths/techniques-paths.yml | 53 +++++++++++++++++++ 11 files changed, 560 insertions(+) diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 86c6a957..6d7d9c2e 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -95,6 +95,9 @@ paths: /api/techniques/{stixId}/modified/{modified}: $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1modified~1{modified}' + /api/techniques/{stixId}/revoke: + $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1revoke' + /api/techniques/{stixId}/modified/{modified}/tactics: $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1modified~1{modified}~1tactics' @@ -111,6 +114,9 @@ paths: /api/tactics/{stixId}/modified/{modified}/techniques: $ref: 'paths/tactics-paths.yml#/paths/~1api~1tactics~1{stixId}~1modified~1{modified}~1techniques' + /api/tactics/{stixId}/revoke: + $ref: 'paths/tactics-paths.yml#/paths/~1api~1tactics~1{stixId}~1revoke' + # Groups /api/groups: $ref: 'paths/groups-paths.yml#/paths/~1api~1groups' @@ -121,6 +127,9 @@ paths: /api/groups/{stixId}/modified/{modified}: $ref: 'paths/groups-paths.yml#/paths/~1api~1groups~1{stixId}~1modified~1{modified}' + /api/groups/{stixId}/revoke: + $ref: 'paths/groups-paths.yml#/paths/~1api~1groups~1{stixId}~1revoke' + # Campaigns /api/campaigns: $ref: 'paths/campaigns-paths.yml#/paths/~1api~1campaigns' @@ -131,6 +140,9 @@ paths: /api/campaigns/{stixId}/modified/{modified}: $ref: 'paths/campaigns-paths.yml#/paths/~1api~1campaigns~1{stixId}~1modified~1{modified}' + /api/campaigns/{stixId}/revoke: + $ref: 'paths/campaigns-paths.yml#/paths/~1api~1campaigns~1{stixId}~1revoke' + # Matrices /api/matrices: $ref: 'paths/matrices-paths.yml#/paths/~1api~1matrices' @@ -144,6 +156,9 @@ paths: /api/matrices/{stixId}/modified/{modified}/techniques: $ref: 'paths/matrices-paths.yml#/paths/~1api~1matrices~1{stixId}~1modified~1{modified}~1techniques' + /api/matrices/{stixId}/revoke: + $ref: 'paths/matrices-paths.yml#/paths/~1api~1matrices~1{stixId}~1revoke' + # Identities /api/identities: $ref: 'paths/identities-paths.yml#/paths/~1api~1identities' @@ -171,6 +186,9 @@ paths: /api/software/{stixId}/modified/{modified}: $ref: 'paths/software-paths.yml#/paths/~1api~1software~1{stixId}~1modified~1{modified}' + /api/software/{stixId}/revoke: + $ref: 'paths/software-paths.yml#/paths/~1api~1software~1{stixId}~1revoke' + # Mitigations /api/mitigations: $ref: 'paths/mitigations-paths.yml#/paths/~1api~1mitigations' @@ -181,6 +199,9 @@ paths: /api/mitigations/{stixId}/modified/{modified}: $ref: 'paths/mitigations-paths.yml#/paths/~1api~1mitigations~1{stixId}~1modified~1{modified}' + /api/mitigations/{stixId}/revoke: + $ref: 'paths/mitigations-paths.yml#/paths/~1api~1mitigations~1{stixId}~1revoke' + # Data Sources /api/data-sources: $ref: 'paths/data-sources-paths.yml#/paths/~1api~1data-sources' @@ -191,6 +212,9 @@ paths: /api/data-sources/{stixId}/modified/{modified}: $ref: 'paths/data-sources-paths.yml#/paths/~1api~1data-sources~1{stixId}~1modified~1{modified}' + /api/data-sources/{stixId}/revoke: + $ref: 'paths/data-sources-paths.yml#/paths/~1api~1data-sources~1{stixId}~1revoke' + # Analytics /api/analytics: $ref: 'paths/analytics-paths.yml#/paths/~1api~1analytics' @@ -227,6 +251,9 @@ paths: /api/data-components/{stixId}/modified/{modified}: $ref: 'paths/data-components-paths.yml#/paths/~1api~1data-components~1{stixId}~1modified~1{modified}' + /api/data-components/{stixId}/revoke: + $ref: 'paths/data-components-paths.yml#/paths/~1api~1data-components~1{stixId}~1revoke' + # Assets /api/assets: $ref: 'paths/assets-paths.yml#/paths/~1api~1assets' @@ -237,6 +264,9 @@ paths: /api/assets/{stixId}/modified/{modified}: $ref: 'paths/assets-paths.yml#/paths/~1api~1assets~1{stixId}~1modified~1{modified}' + /api/assets/{stixId}/revoke: + $ref: 'paths/assets-paths.yml#/paths/~1api~1assets~1{stixId}~1revoke' + # Relationships /api/relationships: $ref: 'paths/relationships-paths.yml#/paths/~1api~1relationships' diff --git a/app/api/definitions/paths/assets-paths.yml b/app/api/definitions/paths/assets-paths.yml index b4a649f1..5f8d1585 100644 --- a/app/api/definitions/paths/assets-paths.yml +++ b/app/api/definitions/paths/assets-paths.yml @@ -295,3 +295,56 @@ paths: description: 'The asset was successfully deleted.' '404': description: 'An asset with the requested STIX id and modified date was not found.' + + /api/assets/{stixId}/revoke: + post: + summary: 'Revoke a asset' + operationId: 'asset-revoke' + description: | + Revokes the asset identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Assets' + parameters: + - name: stixId + in: path + description: 'STIX id of the asset to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The asset was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The asset or revoking object was not found.' + '409': + description: 'The asset is already revoked.' diff --git a/app/api/definitions/paths/campaigns-paths.yml b/app/api/definitions/paths/campaigns-paths.yml index 3661799b..e643c3aa 100644 --- a/app/api/definitions/paths/campaigns-paths.yml +++ b/app/api/definitions/paths/campaigns-paths.yml @@ -273,3 +273,56 @@ paths: description: 'The campaign was successfully deleted.' '404': description: 'A campaign with the requested STIX id and modified date was not found.' + + /api/campaigns/{stixId}/revoke: + post: + summary: 'Revoke a campaign' + operationId: 'campaign-revoke' + description: | + Revokes the campaign identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Campaigns' + parameters: + - name: stixId + in: path + description: 'STIX id of the campaign to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The campaign was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The campaign or revoking object was not found.' + '409': + description: 'The campaign is already revoked.' diff --git a/app/api/definitions/paths/data-components-paths.yml b/app/api/definitions/paths/data-components-paths.yml index cf304548..2c8fa0b4 100644 --- a/app/api/definitions/paths/data-components-paths.yml +++ b/app/api/definitions/paths/data-components-paths.yml @@ -327,3 +327,56 @@ paths: description: 'The data component was successfully deleted.' '404': description: 'A data component with the requested STIX id and modified date was not found.' + + /api/data-components/{stixId}/revoke: + post: + summary: 'Revoke a data component' + operationId: 'data-component-revoke' + description: | + Revokes the data component identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Data Components' + parameters: + - name: stixId + in: path + description: 'STIX id of the data component to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The data component was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The data component or revoking object was not found.' + '409': + description: 'The data component is already revoked.' diff --git a/app/api/definitions/paths/data-sources-paths.yml b/app/api/definitions/paths/data-sources-paths.yml index a7f7a342..2b609476 100644 --- a/app/api/definitions/paths/data-sources-paths.yml +++ b/app/api/definitions/paths/data-sources-paths.yml @@ -311,3 +311,56 @@ paths: description: 'The data source was successfully deleted.' '404': description: 'A data source with the requested STIX id and modified date was not found.' + + /api/data-sources/{stixId}/revoke: + post: + summary: 'Revoke a data source' + operationId: 'data-source-revoke' + description: | + Revokes the data source identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Data Sources' + parameters: + - name: stixId + in: path + description: 'STIX id of the data source to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The data source was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The data source or revoking object was not found.' + '409': + description: 'The data source is already revoked.' diff --git a/app/api/definitions/paths/groups-paths.yml b/app/api/definitions/paths/groups-paths.yml index a9d76466..4b736e37 100644 --- a/app/api/definitions/paths/groups-paths.yml +++ b/app/api/definitions/paths/groups-paths.yml @@ -273,3 +273,56 @@ paths: description: 'The group was successfully deleted.' '404': description: 'A group with the requested STIX id and modified date was not found.' + + /api/groups/{stixId}/revoke: + post: + summary: 'Revoke a group' + operationId: 'group-revoke' + description: | + Revokes the group identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Groups' + parameters: + - name: stixId + in: path + description: 'STIX id of the group to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The group was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The group or revoking object was not found.' + '409': + description: 'The group is already revoked.' diff --git a/app/api/definitions/paths/matrices-paths.yml b/app/api/definitions/paths/matrices-paths.yml index a3ef13d0..f7f84956 100644 --- a/app/api/definitions/paths/matrices-paths.yml +++ b/app/api/definitions/paths/matrices-paths.yml @@ -300,3 +300,56 @@ paths: description: 'The techniques and subtechniques of a matrix matching the STIX id and modified date.' '404': description: 'A matrix with the requested STIX id and modified date was not found.' + + /api/matrices/{stixId}/revoke: + post: + summary: 'Revoke a matrix' + operationId: 'matrix-revoke' + description: | + Revokes the matrix identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Matrices' + parameters: + - name: stixId + in: path + description: 'STIX id of the matrix to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The matrix was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The matrix or revoking object was not found.' + '409': + description: 'The matrix is already revoked.' diff --git a/app/api/definitions/paths/mitigations-paths.yml b/app/api/definitions/paths/mitigations-paths.yml index dde55014..4189babd 100644 --- a/app/api/definitions/paths/mitigations-paths.yml +++ b/app/api/definitions/paths/mitigations-paths.yml @@ -285,3 +285,56 @@ paths: description: 'The mitigation was successfully deleted.' '404': description: 'A mitigation with the requested STIX id and modified date was not found.' + + /api/mitigations/{stixId}/revoke: + post: + summary: 'Revoke a mitigation' + operationId: 'mitigation-revoke' + description: | + Revokes the mitigation identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Mitigations' + parameters: + - name: stixId + in: path + description: 'STIX id of the mitigation to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The mitigation was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The mitigation or revoking object was not found.' + '409': + description: 'The mitigation is already revoked.' diff --git a/app/api/definitions/paths/software-paths.yml b/app/api/definitions/paths/software-paths.yml index 7a8d7da2..e8d1fe25 100644 --- a/app/api/definitions/paths/software-paths.yml +++ b/app/api/definitions/paths/software-paths.yml @@ -295,3 +295,56 @@ paths: description: 'The software object was successfully deleted.' '404': description: 'A software object with the requested STIX id and modified date was not found.' + + /api/software/{stixId}/revoke: + post: + summary: 'Revoke a software' + operationId: 'software-revoke' + description: | + Revokes the software identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Software' + parameters: + - name: stixId + in: path + description: 'STIX id of the software to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The software was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The software or revoking object was not found.' + '409': + description: 'The software is already revoked.' diff --git a/app/api/definitions/paths/tactics-paths.yml b/app/api/definitions/paths/tactics-paths.yml index c810c02a..f402dfb9 100644 --- a/app/api/definitions/paths/tactics-paths.yml +++ b/app/api/definitions/paths/tactics-paths.yml @@ -342,3 +342,56 @@ paths: $ref: '../components/techniques.yml#/components/schemas/technique' '404': description: 'A tactic with the requested STIX id and modified date was not found.' + + /api/tactics/{stixId}/revoke: + post: + summary: 'Revoke a tactic' + operationId: 'tactic-revoke' + description: | + Revokes the tactic identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Tactics' + parameters: + - name: stixId + in: path + description: 'STIX id of the tactic to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The tactic was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The tactic or revoking object was not found.' + '409': + description: 'The tactic is already revoked.' diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index 6d8dc412..92b21ad7 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -366,3 +366,56 @@ paths: $ref: '../components/tactics.yml#/components/schemas/tactic' '404': description: 'A technique with the requested STIX id and modified date was not found.' + + /api/techniques/{stixId}/revoke: + post: + summary: 'Revoke a technique' + operationId: 'technique-revoke' + description: | + Revokes the technique identified by the stixId path parameter in favor of the revoking object specified in the request body. + Optionally transfers relationships from the revoked object to the revoking object. + tags: + - 'Techniques' + parameters: + - name: stixId + in: path + description: 'STIX id of the technique to revoke' + required: true + schema: + type: string + - name: preserveRelationships + in: query + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + schema: + type: boolean + default: false + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - revoking + properties: + revoking: + type: object + required: + - stixId + - modified + properties: + stixId: + type: string + description: 'STIX id of the revoking (replacement) object' + modified: + type: string + description: 'Modified timestamp of the specific version of the revoking object' + responses: + '200': + description: 'The technique was successfully revoked.' + '400': + description: 'Missing or invalid parameters.' + '404': + description: 'The technique or revoking object was not found.' + '409': + description: 'The technique is already revoked.' From 97da4695eb991902f92a47d2f862d5014d26e6b2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:19:47 -0400 Subject: [PATCH 229/370] feat: implement new event types for revoke operations --- app/lib/event-constants.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index 5d4d8f39..932c4460 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -85,6 +85,19 @@ module.exports = Object.freeze({ ASSET_UPDATED: 'x-mitre-asset::updated', ASSET_DELETED: 'x-mitre-asset::deleted', + // Revocation Events + ATTACK_PATTERN_REVOKED: 'attack-pattern::revoked', + TACTIC_REVOKED: 'x-mitre-tactic::revoked', + COURSE_OF_ACTION_REVOKED: 'course-of-action::revoked', + INTRUSION_SET_REVOKED: 'intrusion-set::revoked', + MALWARE_REVOKED: 'malware::revoked', + TOOL_REVOKED: 'tool::revoked', + CAMPAIGN_REVOKED: 'campaign::revoked', + DATA_SOURCE_REVOKED: 'x-mitre-data-source::revoked', + DATA_COMPONENT_REVOKED: 'x-mitre-data-component::revoked', + MATRIX_REVOKED: 'x-mitre-matrix::revoked', + ASSET_REVOKED: 'x-mitre-asset::revoked', + // ============================================================================ // Cross-Document Events // Emitted by Manager classes when changes affect multiple documents From b908474617e9916780a4a609c23773f700dffe41 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:28:47 -0400 Subject: [PATCH 230/370] feat: implement new methods for retrieving and deleting relationship entities --- app/repository/relationships-repository.js | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/app/repository/relationships-repository.js b/app/repository/relationships-repository.js index 5bbba914..24e1ef73 100644 --- a/app/repository/relationships-repository.js +++ b/app/repository/relationships-repository.js @@ -129,6 +129,50 @@ class RelationshipsRepository extends BaseRepository { return await this.model.aggregate(aggregation).exec(); } + /** + * Retrieve the latest version of all relationships where source_ref or target_ref matches the given STIX ID + * @param {string} stixId - The STIX ID to match against source_ref and target_ref + * @returns {Promise} Array of latest-version relationship documents + */ + async retrieveAllBySourceOrTarget(stixId) { + try { + const aggregation = [ + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { + $match: { + $or: [{ 'stix.source_ref': stixId }, { 'stix.target_ref': stixId }], + }, + }, + ]; + return await this.model.aggregate(aggregation).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + /** + * Delete all relationship documents (all versions) where source_ref or target_ref matches, + * excluding relationships with specified STIX IDs + * @param {string} stixId - The STIX ID to match against source_ref and target_ref + * @param {Array} excludeStixIds - STIX IDs of relationships to exclude from deletion + * @returns {Promise<{deletedCount: number}>} Deletion result + */ + async deleteManyBySourceOrTarget(stixId, excludeStixIds = []) { + try { + const query = { + $or: [{ 'stix.source_ref': stixId }, { 'stix.target_ref': stixId }], + }; + if (excludeStixIds.length > 0) { + query['stix.id'] = { $nin: excludeStixIds }; + } + return await this.model.deleteMany(query).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + async retrieveParallelRelationships() { const all_relationships = await this.retrieveAll({ versions: 'latest', From 4cf91336c3fe88333042f7f8b9d922fd17049099 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:29:40 -0400 Subject: [PATCH 231/370] feat: implement service layer logic for new revoke workflow + new lifecycle hooks --- app/services/meta-classes/base.service.js | 273 ++++++++++++++++++++- app/services/meta-classes/hooks.service.js | 46 ++++ 2 files changed, 318 insertions(+), 1 deletion(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 578f4a83..993fcb1f 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -17,6 +17,10 @@ const { OrganizationIdentityNotSetError, InvalidPostOperationError, ValidationError, + BadRequestError, + NotFoundError, + AlreadyRevokedError, + SelfRevocationError, } = require('../../exceptions'); const { getSchema, processValidationIssues } = require('../system/validate-service'); const ServiceWithHooks = require('./hooks.service'); @@ -326,7 +330,7 @@ class BaseService extends ServiceWithHooks { * * Future additions: 'id', 'created', 'modified' (when server takes control of these) */ - static ALWAYS_STRIPPED_STIX_FIELDS = ['x_mitre_attack_spec_version']; + static ALWAYS_STRIPPED_STIX_FIELDS = ['x_mitre_attack_spec_version', 'revoked']; /** * Silently strips universally server-controlled fields from client input. @@ -365,6 +369,29 @@ class BaseService extends ServiceWithHooks { } } + /** + * Coerces any STIX date fields that are JavaScript Date objects into ISO-8601 strings. + * + * Mongoose schemas define timestamp fields (created, modified, start_time, stop_time) + * as `{ type: Date }`, so documents retrieved from MongoDB carry JS Date objects. + * The ADM validation layer (Zod) expects RFC3339 strings. This method bridges that + * gap so that data originating from the repository can safely pass through create() + * without manual per-call-site coercion. + * + * @param {Object} data - The request data ({ stix, workspace }) + */ + normalizeDateFields(data) { + const stix = data.stix; + if (!stix) return; + + const dateFields = ['created', 'modified', 'start_time', 'stop_time']; + for (const field of dateFields) { + if (stix[field] instanceof Date) { + stix[field] = stix[field].toISOString(); + } + } + } + /** * Validates the fully-composed STIX object against the ADM schema. * @@ -445,6 +472,7 @@ class BaseService extends ServiceWithHooks { // 2. COMPOSE OBJECT // ────────────────────────────────────────────── this.stripServerControlledFields(data, options); + this.normalizeDateFields(data); data.stix.external_references = data.stix.external_references || []; // Generate or reuse the ATT&CK ID @@ -491,6 +519,12 @@ class BaseService extends ServiceWithHooks { data.stix.external_references.unshift(attackRef); } + // TODO is this the best approach? + if (data.stix.external_references.length === 0) { + // remove field + delete data.stix.external_references; + } + // ────────────────────────────────────────────── // 3. SET SERVER-CONTROLLED FIELDS // ────────────────────────────────────────────── @@ -520,6 +554,10 @@ class BaseService extends ServiceWithHooks { // 3b. Metadata fields if (options.userAccountId) { + // TODO is this the best approach? We should explore using a DTO or similar pattern to avoid mutating the input data object directly + if (!data.workspace.workflow) { + data.workspace.workflow = {}; + } data.workspace.workflow.created_by_user_account = options.userAccountId; } await this.setDefaultMarkingDefinitionsForObject(data); @@ -635,6 +673,7 @@ class BaseService extends ServiceWithHooks { // 2. COMPOSE OBJECT // ────────────────────────────────────────────── this.stripServerControlledFields(data, options); + this.normalizeDateFields(data); // Compose server-controlled fields from existing document data.stix.x_mitre_attack_spec_version = document.stix.x_mitre_attack_spec_version; @@ -711,6 +750,238 @@ class BaseService extends ServiceWithHooks { return document; } + // ============================ + // Revoke Operation + // ============================ + + /** + * Revokes an object (Object A) in favor of another object (Object B). + * + * Workflow: + * 1. Validate inputs + * 2. Retrieve objects A and B + * 3. Lifecycle hook: beforeRevoke + * 4. Mark Object A as revoked (creates a new version via this.create) + * 5. Create a revoked-by relationship (A → B) + * 6. Handle relationships (transfer to B if preserveRelationships, then delete originals) + * 7. Lifecycle hook: afterRevoke + * 8. Emit revoked event + * 9. Return result + * + * @param {string} stixId - The STIX ID of the object to revoke (Object A) + * @param {Object} data - Request body containing { revoking: { stixId, modified } } + * @param {Object} [options] - Options + * @param {boolean} [options.preserveRelationships] - If true, clone relationships to Object B before deleting + * @param {string} [options.userAccountId] - The authenticated user's account ID + * @returns {Object} Result with revokedObject, revokedByRelationship, relationshipsSummary + */ + async revoke(stixId, data, options = {}) { + logger.info( + `REVOKING ${stixId} in favor of ${data?.revoking?.stixId} (preserveRelationships: ${options.preserveRelationships})`, + ); + + // Lazy-load to avoid circular dependency + const relationshipsService = require('../stix/relationships-service'); + const relationshipsRepository = require('../../repository/relationships-repository'); + + // ────────────────────────────────────────────── + // 1. VALIDATE INPUTS + // ────────────────────────────────────────────── + if (!stixId) { + throw new MissingParameterError('stixId'); + } + if (!data?.revoking?.stixId) { + throw new MissingParameterError('revoking.stixId'); + } + if (!data?.revoking?.modified) { + throw new MissingParameterError('revoking.modified'); + } + if (stixId === data.revoking.stixId) { + throw new SelfRevocationError(); + } + + // ────────────────────────────────────────────── + // 2. RETRIEVE OBJECTS + // ────────────────────────────────────────────── + const objectA = await this.repository.retrieveLatestByStixId(stixId); + if (!objectA) { + throw new NotFoundError({ details: `Object A with stixId ${stixId} not found` }); + } + if (objectA.stix.revoked === true) { + throw new AlreadyRevokedError({ details: `Object ${stixId} is already revoked` }); + } + + const objectB = await this.repository.retrieveOneByVersion( + data.revoking.stixId, + data.revoking.modified, + ); + if (!objectB) { + throw new NotFoundError({ + details: `Object B with stixId ${data.revoking.stixId} and modified ${data.revoking.modified} not found`, + }); + } + if (objectB.stix.type !== this.type) { + throw new BadRequestError({ + details: `Revoking object must be of the same type (${this.type}), got ${objectB.stix.type}`, + }); + } + + // ────────────────────────────────────────────── + // 3. LIFECYCLE HOOK: beforeRevoke + // ────────────────────────────────────────────── + await this.beforeRevoke(objectA, objectB, options); + + // ────────────────────────────────────────────── + // 4. MARK OBJECT A AS REVOKED + // ────────────────────────────────────────────── + // Clone Object A and set revoked = true, then persist directly via the repository. + // We bypass this.create() because the object is already fully composed and validated — + // routing it through create() would strip the revoked flag (which is server-controlled). + const objectAData = objectA.toObject ? objectA.toObject() : { ...objectA }; + delete objectAData._id; + delete objectAData.__v; + delete objectAData.__t; + objectAData.stix.revoked = true; + objectAData.stix.modified = new Date().toISOString(); + if (options.userAccountId) { + objectAData.workspace = objectAData.workspace || {}; + objectAData.workspace.workflow = objectAData.workspace.workflow || {}; + objectAData.workspace.workflow.created_by_user_account = options.userAccountId; + } + + const revokedDocument = await this.repository.save(objectAData); + + // ────────────────────────────────────────────── + // 5. CREATE REVOKED-BY RELATIONSHIP + // ────────────────────────────────────────────── + const now = new Date().toISOString(); + const revokedByRelationship = await relationshipsService.create( + { + workspace: { + workflow: {}, + }, + stix: { + type: 'relationship', + spec_version: '2.1', + relationship_type: 'revoked-by', + source_ref: objectA.stix.id, + target_ref: objectB.stix.id, + created: now, + modified: now, + }, + }, + { userAccountId: options.userAccountId }, + ); + + // TODO what if relationshipsService.create fails after we've already marked Object A as revoked? + // We should have error handling to attempt to roll back the revoked status if the relationship + // creation fails, to avoid leaving the system in a broken state where Object A is revoked but + // there's no link to Object B. This could be done with a try/catch around the relationship creation, + // and in the catch block we would attempt to set revoked back to false on Object A and save it again. + // We would also need to handle potential errors in that rollback attempt and log them appropriately. + + // ────────────────────────────────────────────── + // 6. HANDLE RELATIONSHIPS + // ────────────────────────────────────────────── + const relationshipsSummary = { deleted: 0, transferred: 0, warnings: [] }; + + const existingRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( + objectA.stix.id, + ); + + // Exclude the revoked-by relationship we just created + const relationshipsToProcess = existingRelationships.filter( + (rel) => rel.stix.id !== revokedByRelationship.stix.id, + ); + + if (options.preserveRelationships) { + // Build a set of relationship triples (source_ref--relationship_type--target_ref) + // that Object B already participates in, so we can skip duplicates. + const objectBRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( + objectB.stix.id, + ); + const objectBRelTriples = new Set( + objectBRelationships.map( + (r) => `${r.stix.source_ref}--${r.stix.relationship_type}--${r.stix.target_ref}`, + ), + ); + + for (const rel of relationshipsToProcess) { + try { + // TODO here is another use case for a more robust composition layer or a DTO pattern — we are manually cloning and modifying relationship objects, which is error-prone and may not scale well if relationships have more complex fields in the future. A composition layer could handle cloning an existing relationship and substituting references while ensuring all required fields are correctly set. + const relData = { ...rel }; + delete relData._id; + delete relData.__v; + delete relData.__t; + + // Substitute Object B for Object A + if (relData.stix.source_ref === objectA.stix.id) { + relData.stix.source_ref = objectB.stix.id; + } + if (relData.stix.target_ref === objectA.stix.id) { + relData.stix.target_ref = objectB.stix.id; + } + + // Skip if Object B already has an equivalent relationship + const candidateTriple = `${relData.stix.source_ref}--${relData.stix.relationship_type}--${relData.stix.target_ref}`; + if (objectBRelTriples.has(candidateTriple)) { + relationshipsSummary.duplicatesSkipped = + (relationshipsSummary.duplicatesSkipped || 0) + 1; + logger.info( + `Skipping duplicate relationship transfer: ${candidateTriple} already exists on Object B`, + ); + continue; + } + + // Generate a new STIX ID for the cloned relationship + relData.stix.id = `relationship--${uuid.v4()}`; + + await relationshipsService.create(relData, { + userAccountId: options.userAccountId, + }); + relationshipsSummary.transferred++; + + // Track the newly created triple so subsequent iterations don't create duplicates + objectBRelTriples.add(candidateTriple); + } catch (err) { + logger.warn(`Failed to transfer relationship ${rel.stix.id}: ${err.message}`); + relationshipsSummary.warnings.push( + `Failed to transfer relationship ${rel.stix.id}: ${err.message}`, + ); + } + } + } + + // Delete all original relationships referencing Object A (except the revoked-by) + const excludeIds = [revokedByRelationship.stix.id]; + const deleteResult = await relationshipsRepository.deleteManyBySourceOrTarget( + objectA.stix.id, + excludeIds, + ); + relationshipsSummary.deleted = deleteResult.deletedCount || 0; + + // ────────────────────────────────────────────── + // 7. LIFECYCLE HOOK: afterRevoke + // ────────────────────────────────────────────── + await this.afterRevoke(revokedDocument, objectB, options); + + // ────────────────────────────────────────────── + // 8. EMIT EVENT + // ────────────────────────────────────────────── + await this.emitRevokedEvent(revokedDocument, objectB, options); + + // ────────────────────────────────────────────── + // 9. RETURN RESULT + // ────────────────────────────────────────────── + return { + revokedObject: revokedDocument, + revokedByRelationship: revokedByRelationship.toObject + ? revokedByRelationship.toObject() + : revokedByRelationship, + relationshipsSummary, + }; + } + // TODO rename to deleteManyByStixId async deleteById(stixId) { if (!stixId) { diff --git a/app/services/meta-classes/hooks.service.js b/app/services/meta-classes/hooks.service.js index 32217c28..f86b0b68 100644 --- a/app/services/meta-classes/hooks.service.js +++ b/app/services/meta-classes/hooks.service.js @@ -137,6 +137,52 @@ class ServiceWithHooks { async afterDeleteVersionById(_deletedDocument) { /// Default: no-op } + + /** ******************************** REVOKE ******************************** */ + + /** + * Lifecycle hook: Called before revoke() processes the revocation + * Subclasses can override to validate or prepare data + * @param {object} _objectA - The object being revoked + * @param {object} _objectB - The revoking object + * @param {object} _options - Revocation options + */ + async beforeRevoke(_objectA, _objectB, _options) { + // Default: no-op + } + + /** + * Lifecycle hook: Called after revoke() completes + * Subclasses can override to handle post-revocation logic + * @param {object} _revokedDocument - The revoked document + * @param {object} _objectB - The revoking object + * @param {object} _options - Revocation options + */ + async afterRevoke(_revokedDocument, _objectB, _options) { + // Default: no-op + } + + /** + * Emit event after document revocation + * @param {object} revokedDocument - The revoked document + * @param {object} revokingDocument - The revoking document + * @param {object} options - Revocation options + */ + async emitRevokedEvent(revokedDocument, revokingDocument, options) { + const eventName = `${this.type}::revoked`; + + logger.info(`Emitting event '${eventName}' for ${revokedDocument.stix.id}`); + + await EventBus.emit(eventName, { + stixId: revokedDocument.stix.id, + revokedDocument: revokedDocument.toObject ? revokedDocument.toObject() : revokedDocument, + revokingDocument: revokingDocument.toObject ? revokingDocument.toObject() : revokingDocument, + type: this.type, + options, + }); + + logger.info(`Event '${eventName}' emission complete`); + } } module.exports = ServiceWithHooks; From dd851268edd67cb6ebe05d4d0cad911186447b61 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:33:34 -0400 Subject: [PATCH 232/370] fix: a bug where server attempts to create attack external refs for relationships - The ATT&CK website doesn't provide static, routeable pages or URL paths/slugs for relationships. - Therefore, we shouldn't track on them external references to the ATT&CK website. - We implicitly fix this bug by limiting the creation of ATT&CK external refs to only objects that contain valid ATT&CK IDs. - The logic here is that all object types with ATT&CK IDs are routable on the ATT&CK website. --- app/lib/external-reference-builder.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lib/external-reference-builder.js b/app/lib/external-reference-builder.js index 0aabda94..21097f5a 100644 --- a/app/lib/external-reference-builder.js +++ b/app/lib/external-reference-builder.js @@ -124,8 +124,9 @@ function createAttackExternalReference(data) { const attackId = data.workspace?.attack_id; const stixType = data.stix?.type; - if (!attackId && !stixType) { - logger.warn('createAttackExternalReference called with no attackId or stixType'); + if (!attackId) { + // No ATT&CK ID means no external reference to generate. + // This is expected for types like relationships that never have ATT&CK IDs. return null; } From 05e43aea9b44c0101db136c98bc8151386d42d9c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:35:58 -0400 Subject: [PATCH 233/370] test: update all tests that were setting revoked to use new dedicated revoke operation --- .../api/data-sources/data-sources.spec.js | 10 +- app/tests/api/groups/groups.query.spec.js | 135 ++++++++------- .../api/techniques/techniques.query.spec.js | 154 +++++++++--------- 3 files changed, 146 insertions(+), 153 deletions(-) diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index fd6e2e9e..36be097e 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -64,7 +64,7 @@ async function loadDataComponents(baseDataComponent) { let timestamp = new Date().toISOString(); data1.stix.created = timestamp; data1.stix.modified = timestamp; - await dataComponentsService.create(data1); + const created1 = await dataComponentsService.create(data1); const data2 = _.cloneDeep(baseDataComponent); timestamp = new Date().toISOString(); @@ -89,8 +89,12 @@ async function loadDataComponents(baseDataComponent) { timestamp = new Date().toISOString(); data5.stix.created = timestamp; data5.stix.modified = timestamp; - data5.stix.revoked = true; - await dataComponentsService.create(data5); + const created5 = await dataComponentsService.create(data5); + + // Revoke data component 5 using data component 1 as the revoking object + await dataComponentsService.revoke(created5.stix.id, { + revoking: { stixId: created1.stix.id, modified: created1.stix.modified }, + }); } describe('Data Sources API', function () { diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index d5955019..86d87c9a 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -26,53 +26,66 @@ async function readJson(path) { return JSON.parse(data); } -async function configureGroups(baseGroup, userAccountId1, userAccountId2) { - const groups = []; - // x_mitre_deprecated,revoked undefined (user account 1) - const data1a = _.cloneDeep(baseGroup); - data1a.userAccountId = userAccountId1; - groups.push(data1a); - - // x_mitre_deprecated,revoked undefined (user account 2) - const data1b = _.cloneDeep(baseGroup); - data1b.userAccountId = userAccountId2; - groups.push(data1b); - - // x_mitre_deprecated = false, revoked = false - const data2 = _.cloneDeep(baseGroup); - data2.stix.x_mitre_deprecated = false; - data2.stix.revoked = false; - data2.workspace.workflow = { state: 'work-in-progress' }; - data2.userAccountId = userAccountId1; - groups.push(data2); - - // x_mitre_deprecated = true, revoked = false - const data3 = _.cloneDeep(baseGroup); - data3.stix.x_mitre_deprecated = true; - data3.stix.revoked = false; - data3.workspace.workflow = { state: 'awaiting-review' }; - data3.userAccountId = userAccountId1; - groups.push(data3); - - // x_mitre_deprecated = false, revoked = true - const data4 = _.cloneDeep(baseGroup); - data4.stix.x_mitre_deprecated = false; - data4.stix.revoked = true; - data4.workspace.workflow = { state: 'awaiting-review' }; - data4.userAccountId = userAccountId1; - groups.push(data4); - - // multiple versions, last version has x_mitre_deprecated = true, revoked = true - const data5a = _.cloneDeep(baseGroup); +async function configureAndLoadGroups(baseGroup, userAccountId1, userAccountId2) { + // Helper: create a group from config + async function createGroup(overrides, userAccountId) { + const data = _.cloneDeep(baseGroup); + Object.assign(data.stix, overrides.stix || {}); + if (overrides.workspace) { + data.workspace = { ...data.workspace, ...overrides.workspace }; + } + + if (!data.stix.name) { + data.stix.name = `group-${data.stix.x_mitre_deprecated}-undefined`; + } + if (!data.stix.created) { + const timestamp = new Date().toISOString(); + data.stix.created = timestamp; + data.stix.modified = timestamp; + } + + return groupsService.create(data, { import: false, userAccountId }); + } + + // group 1a: x_mitre_deprecated,revoked undefined (user account 1) + const group1a = await createGroup({}, userAccountId1); + + // group 1b: x_mitre_deprecated,revoked undefined (user account 2) + await createGroup({}, userAccountId2); + + // group 2: x_mitre_deprecated = false, state = work-in-progress + await createGroup( + { stix: { x_mitre_deprecated: false }, workspace: { workflow: { state: 'work-in-progress' } } }, + userAccountId1, + ); + + // group 3: x_mitre_deprecated = true, state = awaiting-review + await createGroup( + { stix: { x_mitre_deprecated: true }, workspace: { workflow: { state: 'awaiting-review' } } }, + userAccountId1, + ); + + // group 4: revoked via the revoke workflow (x_mitre_deprecated = false) + // Use group1a as the revoking object so we don't add extra groups to the count + const group4 = await createGroup( + { stix: { x_mitre_deprecated: false }, workspace: { workflow: { state: 'awaiting-review' } } }, + userAccountId1, + ); + await groupsService.revoke(group4.stix.id, { + revoking: { stixId: group1a.stix.id, modified: group1a.stix.modified }, + }); + + // group 5: multiple versions, last version has x_mitre_deprecated = true and is revoked const id = `intrusion-set--${uuid.v4()}`; + const createdTimestamp = new Date().toISOString(); + + const data5a = _.cloneDeep(baseGroup); data5a.stix.id = id; data5a.stix.name = 'multiple-versions'; data5a.workspace.workflow = { state: 'awaiting-review' }; - const createdTimestamp = new Date().toISOString(); data5a.stix.created = createdTimestamp; data5a.stix.modified = createdTimestamp; - data5a.userAccountId = userAccountId1; - groups.push(data5a); + await groupsService.create(data5a, { import: false, userAccountId: userAccountId1 }); await asyncWait(10); // wait so the modified timestamp can change const data5b = _.cloneDeep(baseGroup); @@ -80,43 +93,24 @@ async function configureGroups(baseGroup, userAccountId1, userAccountId2) { data5b.stix.name = 'multiple-versions'; data5b.workspace.workflow = { state: 'awaiting-review' }; data5b.stix.created = createdTimestamp; - let timestamp = new Date().toISOString(); - data5b.stix.modified = timestamp; - data5b.userAccountId = userAccountId1; - groups.push(data5b); + data5b.stix.modified = new Date().toISOString(); + await groupsService.create(data5b, { import: false, userAccountId: userAccountId1 }); await asyncWait(10); + // Create version 5c with deprecated flag const data5c = _.cloneDeep(baseGroup); data5c.stix.id = id; data5c.stix.name = 'multiple-versions'; data5c.workspace.workflow = { state: 'awaiting-review' }; data5c.stix.x_mitre_deprecated = true; - data5c.stix.revoked = true; data5c.stix.created = createdTimestamp; - timestamp = new Date().toISOString(); - data5c.stix.modified = timestamp; - data5c.userAccountId = userAccountId2; - groups.push(data5c); - - // logger.info(JSON.stringify(groups, null, 4)); - - return groups; -} - -async function loadGroups(groups) { - for (const group of groups) { - if (!group.stix.name) { - group.stix.name = `group-${group.stix.x_mitre_deprecated}-${group.stix.revoked}`; - } - - if (!group.stix.created) { - const timestamp = new Date().toISOString(); - group.stix.created = timestamp; - group.stix.modified = timestamp; - } + data5c.stix.modified = new Date().toISOString(); + await groupsService.create(data5c, { import: false, userAccountId: userAccountId2 }); - await groupsService.create(group, { import: false, userAccountId: group.userAccountId }); - } + // Revoke group5 using group1a as the revoking object + await groupsService.revoke(id, { + revoking: { stixId: group1a.stix.id, modified: group1a.stix.modified }, + }); } const userAccountData1 = { @@ -164,8 +158,7 @@ describe('Groups API Queries', function () { userAccount2 = await userAccountsService.create(userAccountData2); const baseGroup = await readJson('./groups.query.json'); - const groups = await configureGroups(baseGroup, userAccount1.id, userAccount2.id); - await loadGroups(groups); + await configureAndLoadGroups(baseGroup, userAccount1.id, userAccount2.id); }); it('GET /api/groups should return 3 of the preloaded groups', async function () { diff --git a/app/tests/api/techniques/techniques.query.spec.js b/app/tests/api/techniques/techniques.query.spec.js index 3f0c8551..7e83f092 100644 --- a/app/tests/api/techniques/techniques.query.spec.js +++ b/app/tests/api/techniques/techniques.query.spec.js @@ -25,60 +25,76 @@ async function readJson(path) { return JSON.parse(data); } -async function configureTechniques(baseTechnique) { - const techniques = []; - // x_mitre_deprecated,revoked undefined - // state undefined - const data1 = _.cloneDeep(baseTechnique); - techniques.push(data1); - - // x_mitre_deprecated = false, revoked = false - // state = work-in-progress - const data2 = _.cloneDeep(baseTechnique); - data2.stix.x_mitre_deprecated = false; - data2.stix.revoked = false; - data2.stix.x_mitre_domains = ['mobile-attack']; - data2.stix.x_mitre_platforms.push('platform-3'); - data2.workspace.workflow = { state: 'work-in-progress' }; - techniques.push(data2); - - // x_mitre_deprecated = true, revoked = false - // state = awaiting-review - const data3 = _.cloneDeep(baseTechnique); - data3.stix.x_mitre_deprecated = true; - data3.stix.revoked = false; - data3.workspace.workflow = { state: 'awaiting-review' }; - techniques.push(data3); - - // x_mitre_deprecated = false, revoked = true - // state = awaiting-review - const data4 = _.cloneDeep(baseTechnique); - data4.stix.x_mitre_deprecated = false; - data4.stix.revoked = true; - data4.workspace.workflow = { state: 'awaiting-review' }; - techniques.push(data4); - - // multiple versions, last version has x_mitre_deprecated = true, revoked = true - // state = awaiting-review - const data5a = _.cloneDeep(baseTechnique); +async function configureAndLoadTechniques(baseTechnique) { + // Helper: create a technique from config + async function createTechnique(overrides) { + const data = _.cloneDeep(baseTechnique); + Object.assign(data.stix, overrides.stix || {}); + if (overrides.workspace) { + data.workspace = { ...data.workspace, ...overrides.workspace }; + } + + if (!data.stix.name) { + data.stix.name = `attack-pattern-${data.stix.x_mitre_deprecated}-undefined`; + } + if (!data.stix.created) { + const timestamp = new Date().toISOString(); + data.stix.created = timestamp; + data.stix.modified = timestamp; + } + + return techniquesService.create(data); + } + + // technique 1: x_mitre_deprecated,revoked undefined, state undefined + const technique1 = await createTechnique({}); + + // technique 2: x_mitre_deprecated = false, state = work-in-progress, mobile-attack domain + await createTechnique({ + stix: { + x_mitre_deprecated: false, + x_mitre_domains: ['mobile-attack'], + x_mitre_platforms: [...baseTechnique.stix.x_mitre_platforms, 'platform-3'], + }, + workspace: { workflow: { state: 'work-in-progress' } }, + }); + + // technique 3: x_mitre_deprecated = true, state = awaiting-review + await createTechnique({ + stix: { x_mitre_deprecated: true }, + workspace: { workflow: { state: 'awaiting-review' } }, + }); + + // technique 4: revoked via the revoke workflow (x_mitre_deprecated = false) + // Use technique1 as the revoking object + const technique4 = await createTechnique({ + stix: { x_mitre_deprecated: false }, + workspace: { workflow: { state: 'awaiting-review' } }, + }); + await techniquesService.revoke(technique4.stix.id, { + revoking: { stixId: technique1.stix.id, modified: technique1.stix.modified }, + }); + + // technique 5: multiple versions, last version deprecated + revoked const id = `attack-pattern--${uuid.v4()}`; + const createdTimestamp = new Date().toISOString(); + + const data5a = _.cloneDeep(baseTechnique); data5a.stix.id = id; data5a.stix.name = 'multiple-versions'; data5a.workspace.workflow = { state: 'awaiting-review' }; - const createdTimestamp = new Date().toISOString(); data5a.stix.created = createdTimestamp; data5a.stix.modified = createdTimestamp; - techniques.push(data5a); + await techniquesService.create(data5a); - await asyncWait(10); // wait so the modified timestamp can change + await asyncWait(10); const data5b = _.cloneDeep(baseTechnique); data5b.stix.id = id; data5b.stix.name = 'multiple-versions'; data5b.workspace.workflow = { state: 'awaiting-review' }; data5b.stix.created = createdTimestamp; - let timestamp = new Date().toISOString(); - data5b.stix.modified = timestamp; - techniques.push(data5b); + data5b.stix.modified = new Date().toISOString(); + await techniquesService.create(data5b); await asyncWait(10); const data5c = _.cloneDeep(baseTechnique); @@ -86,45 +102,26 @@ async function configureTechniques(baseTechnique) { data5c.stix.name = 'multiple-versions'; data5c.workspace.workflow = { state: 'awaiting-review' }; data5c.stix.x_mitre_deprecated = true; - data5c.stix.revoked = true; data5c.stix.created = createdTimestamp; - timestamp = new Date().toISOString(); - data5c.stix.modified = timestamp; - techniques.push(data5c); - - // x_mitre_deprecated,revoked undefined - // state = work-in-progress - const data6 = _.cloneDeep(baseTechnique); - data6.stix.x_mitre_deprecated = false; - data6.stix.revoked = false; - data6.workspace.workflow = { state: 'work-in-progress' }; - techniques.push(data6); - - // x_mitre_deprecated,revoked undefined - // state = reviewed - const data7 = _.cloneDeep(baseTechnique); - data7.stix.x_mitre_deprecated = false; - data7.stix.revoked = false; - data7.workspace.workflow = { state: 'reviewed' }; - techniques.push(data7); - - return techniques; -} + data5c.stix.modified = new Date().toISOString(); + await techniquesService.create(data5c); -async function loadTechniques(techniques) { - for (const technique of techniques) { - if (!technique.stix.name) { - technique.stix.name = `attack-pattern-${technique.stix.x_mitre_deprecated}-${technique.stix.revoked}`; - } + // Revoke technique 5 using technique1 as the revoking object + await techniquesService.revoke(id, { + revoking: { stixId: technique1.stix.id, modified: technique1.stix.modified }, + }); - if (!technique.stix.created) { - const timestamp = new Date().toISOString(); - technique.stix.created = timestamp; - technique.stix.modified = timestamp; - } + // technique 6: x_mitre_deprecated = false, state = work-in-progress + await createTechnique({ + stix: { x_mitre_deprecated: false }, + workspace: { workflow: { state: 'work-in-progress' } }, + }); - await techniquesService.create(technique); - } + // technique 7: x_mitre_deprecated = false, state = reviewed + await createTechnique({ + stix: { x_mitre_deprecated: false }, + workspace: { workflow: { state: 'reviewed' } }, + }); } describe('Techniques Query API', function () { @@ -147,8 +144,7 @@ describe('Techniques Query API', function () { app = await require('../../../index').initializeApp(); const baseTechnique = await readJson('./techniques.query.json'); - const techniques = await configureTechniques(baseTechnique); - await loadTechniques(techniques); + await configureAndLoadTechniques(baseTechnique); // Log into the app passportCookie = await login.loginAnonymous(app); From b8e8b0f74f182178ce1d4206f43a9e811807d50c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:55:58 -0400 Subject: [PATCH 234/370] test: add new techniques.revoke.spec.js to evaluate e2e revoke operations --- .../api/techniques/techniques.revoke.spec.js | 555 ++++++++++++++++++ 1 file changed, 555 insertions(+) create mode 100644 app/tests/api/techniques/techniques.revoke.spec.js diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js new file mode 100644 index 00000000..bda60aad --- /dev/null +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -0,0 +1,555 @@ +const request = require('supertest'); +const { expect } = require('expect'); + +const database = require('../../../lib/database-in-memory'); +const databaseConfiguration = require('../../../lib/database-configuration'); + +const config = require('../../../config/config'); +const login = require('../../shared/login'); +const { cloneForCreate } = require('../../shared/clone-for-create'); + +const logger = require('../../../lib/logger'); +logger.level = 'debug'; + +const initialObjectData = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + name: 'revoke-test-technique', + spec_version: '2.1', + type: 'attack-pattern', + description: 'This technique will be revoked.', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], + x_mitre_is_subtechnique: false, + x_mitre_platforms: ['platform-1'], + }, +}; + +describe('Techniques Revoke API', function () { + let app; + let passportCookie; + + before(async function () { + await database.initializeConnection(); + await databaseConfiguration.checkSystemConfiguration(); + + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = false; + + app = await require('../../../index').initializeApp(); + passportCookie = await login.loginAnonymous(app); + }); + + let techniqueA; + let techniqueB; + + it('POST /api/techniques creates technique A (to be revoked)', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'technique-A', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + techniqueA = res.body; + expect(techniqueA.stix.id).toBeDefined(); + }); + + it('POST /api/techniques creates technique B (the replacement)', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'technique-B', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + techniqueB = res.body; + expect(techniqueB.stix.id).toBeDefined(); + }); + + it('POST /api/techniques/:stixId/revoke returns 400 for self-revocation', async function () { + const res = await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ + revoking: { stixId: techniqueA.stix.id, modified: techniqueA.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + + expect(res.body.message || res.text).toBeDefined(); + }); + + it('POST /api/techniques/:stixId/revoke returns 404 for cross-type revocation (object B not in techniques collection)', async function () { + // Create a tactic (different type) and try to use it as the revoking object. + // Since each type has its own repository, the tactic won't be found in the + // techniques collection, resulting in a 404. + const timestamp = new Date().toISOString(); + const tacticBody = { + workspace: { workflow: {} }, + stix: { + name: 'tactic-cross-type', + spec_version: '2.1', + type: 'x-mitre-tactic', + description: 'A tactic.', + x_mitre_shortname: 'cross-type-test', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + created: timestamp, + modified: timestamp, + }, + }; + const tacticRes = await request(app) + .post('/api/tactics') + .send(tacticBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const tactic = tacticRes.body; + + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ + revoking: { stixId: tactic.stix.id, modified: tactic.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(404); + }); + + it('POST /api/techniques/:stixId/revoke returns 404 when object A is not found', async function () { + await request(app) + .post('/api/techniques/attack-pattern--00000000-0000-0000-0000-000000000000/revoke') + .send({ + revoking: { stixId: techniqueB.stix.id, modified: techniqueB.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(404); + }); + + it('POST /api/techniques/:stixId/revoke returns 404 when object B is not found', async function () { + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ + revoking: { + stixId: 'attack-pattern--00000000-0000-0000-0000-000000000000', + modified: '2026-01-01T00:00:00.000Z', + }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(404); + }); + + it('POST /api/techniques/:stixId/revoke returns 400 when revoking.stixId is missing', async function () { + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ revoking: { modified: '2026-01-01T00:00:00.000Z' } }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + + it('POST /api/techniques/:stixId/revoke returns 400 when revoking.modified is missing', async function () { + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ revoking: { stixId: techniqueB.stix.id } }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + + let revokeResult; + it('POST /api/techniques/:stixId/revoke revokes technique A in favor of technique B', async function () { + const res = await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ + revoking: { stixId: techniqueB.stix.id, modified: techniqueB.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + revokeResult = res.body; + + // Verify the response structure + expect(revokeResult.revokedObject).toBeDefined(); + expect(revokeResult.revokedByRelationship).toBeDefined(); + expect(revokeResult.relationshipsSummary).toBeDefined(); + + // Verify the revoked object + expect(revokeResult.revokedObject.stix.id).toBe(techniqueA.stix.id); + expect(revokeResult.revokedObject.stix.revoked).toBe(true); + + // Verify the revoked-by relationship + expect(revokeResult.revokedByRelationship.stix.relationship_type).toBe('revoked-by'); + expect(revokeResult.revokedByRelationship.stix.source_ref).toBe(techniqueA.stix.id); + expect(revokeResult.revokedByRelationship.stix.target_ref).toBe(techniqueB.stix.id); + }); + + it('GET /api/techniques/:stixId returns the revoked technique with revoked = true', async function () { + const res = await request(app) + .get(`/api/techniques/${techniqueA.stix.id}?versions=latest`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const techniques = res.body; + expect(techniques).toBeDefined(); + expect(techniques.length).toBe(1); + expect(techniques[0].stix.revoked).toBe(true); + }); + + it('GET /api/techniques excludes the revoked technique by default', async function () { + const res = await request(app) + .get('/api/techniques') + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const techniques = res.body; + const revokedIds = techniques.map((t) => t.stix.id); + expect(revokedIds).not.toContain(techniqueA.stix.id); + }); + + it('GET /api/techniques?includeRevoked=true includes the revoked technique', async function () { + const res = await request(app) + .get('/api/techniques?includeRevoked=true') + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const techniques = res.body; + const ids = techniques.map((t) => t.stix.id); + expect(ids).toContain(techniqueA.stix.id); + }); + + it('POST /api/techniques/:stixId/revoke returns 409 when object A is already revoked', async function () { + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ + revoking: { stixId: techniqueB.stix.id, modified: techniqueB.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(409); + }); + + it('POST /api/techniques strips revoked from create requests', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'sneaky-revoke-attempt', + created: timestamp, + modified: timestamp, + revoked: true, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + // The revoked flag should have been stripped + expect(res.body.stix.revoked).not.toBe(true); + }); + + it('PUT /api/techniques strips revoked from update requests', async function () { + const updateData = cloneForCreate(techniqueB); + updateData.stix.revoked = true; + updateData.stix.description = 'Trying to sneak in revoked via update.'; + + const res = await request(app) + .put(`/api/techniques/${techniqueB.stix.id}/modified/${techniqueB.stix.modified}`) + .send(updateData) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + // The revoked flag should have been stripped, description updated + expect(res.body.stix.revoked).not.toBe(true); + expect(res.body.stix.description).toBe('Trying to sneak in revoked via update.'); + }); + + it('POST /api/techniques/:stixId/revoke with preserveRelationships transfers relationships', async function () { + // Create two new techniques: C (to be revoked) and D (replacement) + let timestamp = new Date().toISOString(); + const bodyC = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'technique-C', + created: timestamp, + modified: timestamp, + }, + }; + const resC = await request(app) + .post('/api/techniques') + .send(bodyC) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const techniqueC = resC.body; + + timestamp = new Date().toISOString(); + const bodyD = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'technique-D', + created: timestamp, + modified: timestamp, + }, + }; + const resD = await request(app) + .post('/api/techniques') + .send(bodyD) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const techniqueD = resD.body; + + // Create a relationship involving technique C + timestamp = new Date().toISOString(); + const relBody = { + workspace: { workflow: {} }, + stix: { + type: 'relationship', + spec_version: '2.1', + relationship_type: 'uses', + source_ref: techniqueC.stix.id, + target_ref: techniqueB.stix.id, + created: timestamp, + modified: timestamp, + }, + }; + const relRes = await request(app) + .post('/api/relationships') + .send(relBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const originalRel = relRes.body; + + // Revoke technique C with preserveRelationships=true + const revokeRes = await request(app) + .post(`/api/techniques/${techniqueC.stix.id}/revoke?preserveRelationships=true`) + .send({ + revoking: { stixId: techniqueD.stix.id, modified: techniqueD.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const result = revokeRes.body; + expect(result.relationshipsSummary.transferred).toBe(1); + expect(result.relationshipsSummary.deleted).toBeGreaterThanOrEqual(1); + + // Verify the original relationship was deleted (all versions removed → 404) + await request(app) + .get(`/api/relationships/${originalRel.stix.id}`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(404); + + // Verify a new relationship was created pointing to technique D + const allRelsRes = await request(app) + .get(`/api/relationships?sourceRef=${techniqueD.stix.id}`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + + const transferredRels = allRelsRes.body.filter( + (r) => r.stix.relationship_type === 'uses' && r.stix.target_ref === techniqueB.stix.id, + ); + expect(transferredRels.length).toBe(1); + }); + + it('POST /api/techniques/:stixId/revoke with preserveRelationships skips duplicate relationships', async function () { + // Create technique E (to be revoked) and technique F (replacement) + let timestamp = new Date().toISOString(); + const bodyE = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'technique-E', + created: timestamp, + modified: timestamp, + }, + }; + const resE = await request(app) + .post('/api/techniques') + .send(bodyE) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const techniqueE = resE.body; + + timestamp = new Date().toISOString(); + const bodyF = { + ...initialObjectData, + stix: { + ...initialObjectData.stix, + name: 'technique-F', + created: timestamp, + modified: timestamp, + }, + }; + const resF = await request(app) + .post('/api/techniques') + .send(bodyF) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const techniqueF = resF.body; + + // Create mitigation M1 + timestamp = new Date().toISOString(); + const bodyM = { + workspace: { workflow: { state: 'work-in-progress' } }, + stix: { + name: 'mitigation-dedup-test', + spec_version: '2.1', + type: 'course-of-action', + description: 'Mitigates both E and F.', + created: timestamp, + modified: timestamp, + }, + }; + const resM = await request(app) + .post('/api/mitigations') + .send(bodyM) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const mitigationM = resM.body; + + // Create "mitigates" relationship M1 → E + timestamp = new Date().toISOString(); + const relME = { + workspace: { workflow: {} }, + stix: { + type: 'relationship', + spec_version: '2.1', + relationship_type: 'mitigates', + source_ref: mitigationM.stix.id, + target_ref: techniqueE.stix.id, + created: timestamp, + modified: timestamp, + }, + }; + await request(app) + .post('/api/relationships') + .send(relME) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + // Create "mitigates" relationship M1 → F (pre-existing duplicate) + timestamp = new Date().toISOString(); + const relMF = { + workspace: { workflow: {} }, + stix: { + type: 'relationship', + spec_version: '2.1', + relationship_type: 'mitigates', + source_ref: mitigationM.stix.id, + target_ref: techniqueF.stix.id, + created: timestamp, + modified: timestamp, + }, + }; + const resRelMF = await request(app) + .post('/api/relationships') + .send(relMF) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + const preExistingRel = resRelMF.body; + + // Revoke technique E in favor of technique F with preserveRelationships=true + const revokeRes = await request(app) + .post(`/api/techniques/${techniqueE.stix.id}/revoke?preserveRelationships=true`) + .send({ + revoking: { stixId: techniqueF.stix.id, modified: techniqueF.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const result = revokeRes.body; + + // The duplicate should have been skipped, not transferred + expect(result.relationshipsSummary.transferred).toBe(0); + expect(result.relationshipsSummary.duplicatesSkipped).toBe(1); + + // Verify exactly one "mitigates" relationship exists from M1 → F (the pre-existing one) + const relsRes = await request(app) + .get(`/api/relationships?sourceRef=${mitigationM.stix.id}&targetRef=${techniqueF.stix.id}`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + + const mitigatesRels = relsRes.body.filter( + (r) => r.stix.relationship_type === 'mitigates', + ); + expect(mitigatesRels.length).toBe(1); + expect(mitigatesRels[0].stix.id).toBe(preExistingRel.stix.id); + + // Verify the original M1 → E relationship was deleted + const origRes = await request(app) + .get(`/api/relationships?sourceRef=${mitigationM.stix.id}&targetRef=${techniqueE.stix.id}`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + + const oldMitigatesRels = origRes.body.filter( + (r) => r.stix.relationship_type === 'mitigates', + ); + expect(oldMitigatesRels.length).toBe(0); + }); + + after(async function () { + await database.closeConnection(); + }); +}); From f0839fa95eb5b04f66d7d737a9c7498a21117c04 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:56:14 -0400 Subject: [PATCH 235/370] style: apply formatting --- app/tests/api/techniques/techniques.revoke.spec.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js index bda60aad..9b60f358 100644 --- a/app/tests/api/techniques/techniques.revoke.spec.js +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -530,9 +530,7 @@ describe('Techniques Revoke API', function () { .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); - const mitigatesRels = relsRes.body.filter( - (r) => r.stix.relationship_type === 'mitigates', - ); + const mitigatesRels = relsRes.body.filter((r) => r.stix.relationship_type === 'mitigates'); expect(mitigatesRels.length).toBe(1); expect(mitigatesRels[0].stix.id).toBe(preExistingRel.stix.id); @@ -543,9 +541,7 @@ describe('Techniques Revoke API', function () { .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); - const oldMitigatesRels = origRes.body.filter( - (r) => r.stix.relationship_type === 'mitigates', - ); + const oldMitigatesRels = origRes.body.filter((r) => r.stix.relationship_type === 'mitigates'); expect(oldMitigatesRels.length).toBe(0); }); From a4c1acac6b87eee2a7cfd50ca24dea6a74b2f31c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:37:54 -0400 Subject: [PATCH 236/370] docs: document the revoke workflow --- docs/revoke-workflow.md | 156 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/revoke-workflow.md diff --git a/docs/revoke-workflow.md b/docs/revoke-workflow.md new file mode 100644 index 00000000..91b463ab --- /dev/null +++ b/docs/revoke-workflow.md @@ -0,0 +1,156 @@ +# The 'Revoke Object' workflow + +The Revoke Object workflow allows you to revoke an existing object in the system, which creates a new revoked version of the object with a new STIX ID and updated metadata. The original object remains in the system but is marked as revoked and is not returned in default queries. + +Formerly, this workflow was orchestrated by the frontend, which made multiple API calls to achieve the desired result. Now, the backend has a dedicated endpoint that handles the entire revoke workflow in a single request, simplifying the process and reducing the potential for errors. + +## Usage + +To revoke an object, send a POST request to the following endpoint: + +``` +POST /api/objects/:stixId/revoke +``` +Where `:stixId` is the STIX ID of the object you want to revoke. + +**Request Body**: + +Specify the `id` and `modified` timestamp for the revoked object in the request body. The `id` should be a new STIX ID that follows the standard format (e.g., `attack-pattern--xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`), and the `modified` timestamp should be in RFC3339 format. + +```json +{ + "revoking": { + "stixId": "attack-pattern--00290ac5-551e-44aa-bbd8-c4b913488a6f", + "modified": "2022-10-24T15:09:07.609Z" + } +} +``` + +### Query Parameters + +Optionally, you can set the following query parameter to preserve relationships: + +- `preserveRelationships` (boolean): If set to `true`, the workflow will attempt to preserve existing relationships by substituting the revoked object with the new revoked version in those relationships. If not set or set to `false`, all relationships involving the revoked object will be deleted. Notably, if the revoking object (Object B) already participates in a relationship with the same source, target, and relationship type as an existing relationship of the revoked object (Object A), that relationship will be preserved as-is without creating a new relationship for Object B. + +## Response + +### Success Response + +On success, the API will return a 200 OK response with the following body, which includes the revoked object, the new "revoked-by" relationship, and a summary of how relationships were handled: + +```json +{ + "revokedObject": { + "workspace": { + "workflow": { + "state": "work-in-progress", + "created_by_user_account": "identity--3562fbf3-795f-4955-8e64-6c964f598e1c" + }, + "attack_id": "T0006", + "collections": [], + "embedded_relationships": [] + }, + "stix": { + "type": "attack-pattern", + "spec_version": "2.1", + "id": "attack-pattern--83efdc56-d35f-4508-9f10-152bbfffde79", + "created": "2026-03-27T14:31:52.711Z", + "created_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", + "revoked": true, + "external_references": [ + { + "source_name": "mitre-attack", + "url": "https://attack.mitre.org/techniques/T0006", + "external_id": "T0006" + } + ], + "object_marking_refs": [ + "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" + ], + "modified": "2026-03-27T14:31:52.744Z", + "name": "technique-E", + "description": "This technique will be revoked.", + "kill_chain_phases": [ + { + "kill_chain_name": "kill-chain-name-1", + "phase_name": "phase-1" + } + ], + "x_mitre_attack_spec_version": "3.3.0", + "x_mitre_contributors": [], + "x_mitre_deprecated": false, + "x_mitre_is_subtechnique": false, + "x_mitre_modified_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", + "x_mitre_platforms": [ + "platform-1" + ] + } + }, + "revokedByRelationship": { + "workspace": { + "workflow": { + "created_by_user_account": "identity--3562fbf3-795f-4955-8e64-6c964f598e1c" + }, + "collections": [], + "embedded_relationships": [] + }, + "stix": { + "type": "relationship", + "spec_version": "2.1", + "id": "relationship--64d45634-f855-4fea-b084-33a87858406d", + "created": "2026-03-27T14:31:52.745Z", + "created_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", + "object_marking_refs": [], + "modified": "2026-03-27T14:31:52.745Z", + "relationship_type": "revoked-by", + "source_ref": "attack-pattern--83efdc56-d35f-4508-9f10-152bbfffde79", + "target_ref": "attack-pattern--ab992c5a-4a03-4374-ad15-440fac072760", + "x_mitre_modified_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", + "x_mitre_attack_spec_version": "3.3.0", + "external_references": [] + }, + "_id": "69c694d8eb64093bcd182721", + "__v": 0, + "warnings": [] + }, + "relationshipsSummary": { + "deleted": 1, + "transferred": 0, + "warnings": [], + "duplicatesSkipped": 1 + } +} +``` + + +### Error Responses + +#### 409 Conflict + +If you attempt to revoke an object that has already been revoked, you will receive a 409 Conflict response with the following message: + +```json +{ + "message": "Object has already been revoked", + "details": "Object attack-pattern--00290ac5-551e-44aa-bbd8-c4b913488a6c is already revoked" +} +``` + +#### 404 Not Found + +If you attempt to revoke an object that does not exist, you will receive a 404 Not Found response with the following message: + +```json +{ + "message": "Document not found", + "details": "Object B with stixId attack-pattern--00290ac5-551e-44aa-bbd8-c4b913488a6f and modified 2022-10-24T15:09:07.609Z not found" +} +``` + +#### 400 Self Revocation Error + +If you attempt to revoke an object by revoking with the same STIX ID and modified timestamp as the original object (i.e., self-revocation), you will receive a 400 Bad Request response with the following message: + +```html +"An object cannot revoke itself" +``` \ No newline at end of file From 9a47bc5159325bd333beaec8e0fb42e40f69910e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:03:59 -0400 Subject: [PATCH 237/370] docs: major reorg of markdown documentation Docs reorganized into 3 audience based subdirs: - docs/user - docs/developer - docs/admin docs/images and docs/legacy are unchanged. All files now use lowercase kebab-case. COLLECTIONS_V2 renamed to release-tracks/. Numeric prefixes dropped. Internal cross-ref links updated. --- README.md | 2 +- USAGE.md | 4 +- docs/README.md | 61 +++++++++++++++---- docs/{ => admin}/authentication/README.md | 6 +- docs/{ => admin}/authentication/authentik.md | 0 .../authentication/configuration.md | 0 docs/{ => admin}/authentication/keycloak.md | 0 docs/{ => admin}/authentication/okta.md | 0 .../authentication/testing-verification.md | 0 docs/{ => admin}/configuration.md | 0 .../cross-service-reads-pattern.md} | 2 +- docs/{ => developer}/data-model.md | 0 .../event-bus-architecture.md} | 0 .../implementation-approach.md} | 0 .../lifecycle-hooks-guide.md} | 0 .../release-tracks/entities.md} | 0 .../release-tracks/error-handling.md} | 0 .../release-tracks/implementation-notes.md} | 0 .../release-tracks/member-sync-strategies.md} | 8 +-- .../service-exception-middleware.md} | 0 ...-versioning-and-embedded-relationships.md} | 4 +- .../task-scheduler.md} | 0 .../release-tracks/api-reference.md} | 20 +++--- .../release-tracks/output-formats.md} | 0 .../release-tracks/release-workflow.md} | 8 +-- .../release-tracks/summary.md} | 8 +-- .../release-tracks/terminology.md} | 6 +- .../release-tracks/versioning.md} | 2 +- .../release-tracks/virtual-tracks.md} | 0 .../release-tracks/workflow-examples.md} | 0 docs/{ => user}/revoke-workflow.md | 0 31 files changed, 83 insertions(+), 48 deletions(-) rename docs/{ => admin}/authentication/README.md (95%) rename docs/{ => admin}/authentication/authentik.md (100%) rename docs/{ => admin}/authentication/configuration.md (100%) rename docs/{ => admin}/authentication/keycloak.md (100%) rename docs/{ => admin}/authentication/okta.md (100%) rename docs/{ => admin}/authentication/testing-verification.md (100%) rename docs/{ => admin}/configuration.md (100%) rename docs/{CROSS_SERVICE_READS_PATTERN.md => developer/cross-service-reads-pattern.md} (99%) rename docs/{ => developer}/data-model.md (100%) rename docs/{EVENT_BUS_ARCHITECTURE.md => developer/event-bus-architecture.md} (100%) rename docs/{IMPLEMENTATION_APPROACH.md => developer/implementation-approach.md} (100%) rename docs/{LIFECYCLE_HOOKS_GUIDE.md => developer/lifecycle-hooks-guide.md} (100%) rename docs/{COLLECTIONS_V2/06_ENTITIES.md => developer/release-tracks/entities.md} (100%) rename docs/{COLLECTIONS_V2/99_ERROR_HANDLING.md => developer/release-tracks/error-handling.md} (100%) rename docs/{COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md => developer/release-tracks/implementation-notes.md} (100%) rename docs/{COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md => developer/release-tracks/member-sync-strategies.md} (98%) rename docs/{SERVICE_EXCEPTION_MIDDLEWARE.md => developer/service-exception-middleware.md} (100%) rename docs/{STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md => developer/stix-versioning-and-embedded-relationships.md} (99%) rename docs/{TASK_SCHEDULER.md => developer/task-scheduler.md} (100%) rename docs/{COLLECTIONS_V2/00_API_REFERENCE.md => user/release-tracks/api-reference.md} (96%) rename docs/{COLLECTIONS_V2/07_OUTPUT_FORMATS.md => user/release-tracks/output-formats.md} (100%) rename docs/{COLLECTIONS_V2/05_RELEASE_WORKFLOW.md => user/release-tracks/release-workflow.md} (98%) rename docs/{COLLECTIONS_V2/01_SUMMARY.md => user/release-tracks/summary.md} (94%) rename docs/{COLLECTIONS_V2/02_TERMINOLOGY.md => user/release-tracks/terminology.md} (98%) rename docs/{COLLECTIONS_V2/03_VERSIONING.md => user/release-tracks/versioning.md} (97%) rename docs/{COLLECTIONS_V2/04_VIRTUAL_TRACKS.md => user/release-tracks/virtual-tracks.md} (100%) rename docs/{COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md => user/release-tracks/workflow-examples.md} (100%) rename docs/{ => user}/revoke-workflow.md (100%) diff --git a/README.md b/README.md index e37b1a3c..ed9176db 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ For a full ATT&CK Workbench deployment, including the frontend application, see - [Usage Guide](USAGE.md): Comprehensive instructions for installing, configuring, and administering the REST API - [Contributing Guide](CONTRIBUTING.md): Information for developers about contributing to the project -- [Data Model](docs/data-model.md): Technical details about the data models used in the application +- [Data Model](docs/developer/data-model.md): Technical details about the data models used in the application ## Technical Information diff --git a/USAGE.md b/USAGE.md index c1d33b17..14db2443 100644 --- a/USAGE.md +++ b/USAGE.md @@ -88,7 +88,7 @@ docker run -p 3000:3000 -d \ ghcr.io/center-for-threat-informed-defense/attack-workbench-rest-api:latest ``` -More infomation about configuration options is in the [configuration file documentation](./docs/configuration.md). +More infomation about configuration options is in the [configuration file documentation](./docs/admin/configuration.md). ### Manual Installation @@ -173,7 +173,7 @@ Example configuration file: ## Authentication The REST API has several authentication options. -Read all about them in the [authentication docs](./docs/authentication/README.md). +Read all about them in the [authentication docs](./docs/admin/authentication/README.md). ## User Management diff --git a/docs/README.md b/docs/README.md index f21c0b99..5d9bb62c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,29 +5,64 @@ This directory contains supplementary technical documentation for the ATT&CK Wor - [USAGE.md](../USAGE.md): Comprehensive usage instructions - [CONTRIBUTING.md](../CONTRIBUTING.md): Guide for developers -## Technical Reference Documentation +## User Documentation -The following documents provide detailed technical information about specific aspects of the REST API: +Guides for consumers of the REST API — endpoints, workflows, and terminology. -- [Data Model](data-model.md): Detailed explanation of the database schema and STIX object structure +- [Revoke Workflow](user/revoke-workflow.md): How to revoke ATT&CK objects via the API -## Authentication Configuration +### Release Tracks -Comprehensive guides for configuring user authentication with various identity providers: +- [API Reference](user/release-tracks/api-reference.md): Complete endpoint reference for Release Tracks V2 +- [Summary](user/release-tracks/summary.md): High-level design summary and problem statement +- [Terminology](user/release-tracks/terminology.md): Complete terminology guide +- [Versioning](user/release-tracks/versioning.md): Git-inspired versioning and release process +- [Virtual Tracks](user/release-tracks/virtual-tracks.md): Virtual release tracks (aggregations) +- [Release Workflow](user/release-tracks/release-workflow.md): Workflow integration and candidacy +- [Output Formats](user/release-tracks/output-formats.md): Output format specifications +- [Workflow Examples](user/release-tracks/workflow-examples.md): End-to-end workflow examples -- [Authentication Overview](authentication/README.md): Introduction and quick start guide -- [Authentik Setup](authentication/authentik.md): Step-by-step guide for Authentik configuration -- [Keycloak Setup](authentication/keycloak.md): Step-by-step guide for Keycloak configuration -- [Okta Setup](authentication/okta.md): Step-by-step guide for Okta configuration +## Developer Documentation -## Legacy Documentation +Architecture, patterns, and implementation details for contributors. + +- [Data Model](developer/data-model.md): Database schema and STIX object structure +- [Event Bus Architecture](developer/event-bus-architecture.md): Event-driven architecture for cross-document dependencies +- [Lifecycle Hooks Guide](developer/lifecycle-hooks-guide.md): Service lifecycle hooks pattern +- [Cross-Service Reads Pattern](developer/cross-service-reads-pattern.md): Cross-service communication patterns +- [Implementation Approach](developer/implementation-approach.md): Detailed implementation pattern for event-driven services +- [Service Exception Middleware](developer/service-exception-middleware.md): Global error handler middleware +- [STIX Versioning and Embedded Relationships](developer/stix-versioning-and-embedded-relationships.md): How STIX versioning interacts with embedded relationships +- [Task Scheduler](developer/task-scheduler.md): Task scheduler implementation + +### Release Tracks (Internals) + +- [Entities](developer/release-tracks/entities.md): Database schemas and data models +- [Member Sync Strategies](developer/release-tracks/member-sync-strategies.md): Automatic tracking of member object revisions +- [Error Handling](developer/release-tracks/error-handling.md): Error handling patterns +- [Implementation Notes](developer/release-tracks/implementation-notes.md): Implementation notes and decisions + +## Admin Documentation -The following documents contain additional information that may be useful for specific scenarios but are not part of the primary documentation: +Configuration, deployment, and identity provider setup. + +- [Configuration](admin/configuration.md): Complete configuration guide (environment variables, JSON files) + +### Authentication + +- [Authentication Overview](admin/authentication/README.md): Introduction and quick start guide +- [Authentication Configuration](admin/authentication/configuration.md): REST API authentication configuration +- [Authentik Setup](admin/authentication/authentik.md): Step-by-step guide for Authentik +- [Keycloak Setup](admin/authentication/keycloak.md): Step-by-step guide for Keycloak +- [Okta Setup](admin/authentication/okta.md): Step-by-step guide for Okta +- [Testing & Verification](admin/authentication/testing-verification.md): Verify authentication is working + +## Legacy Documentation - [Authentication Details](legacy/authentication.md): Technical details about authentication mechanisms - [User Management](legacy/user-management.md): Detailed information about user accounts and permissions - [Docker Deployment](legacy/docker.md): Legacy instructions for Docker deployment -- [Link-by-ID Mechanism](legacy/link-by-id.md): Technical details about object linking in the data model +- [Link-by-ID Mechanism](legacy/link-by-id.md): Technical details about object linking ## API Documentation @@ -37,4 +72,4 @@ Interactive API documentation is available when running the application in devel - [GitHub Repository](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api) - [Frontend Repository](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend) -- [Issue Tracker](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues) \ No newline at end of file +- [Issue Tracker](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues) diff --git a/docs/authentication/README.md b/docs/admin/authentication/README.md similarity index 95% rename from docs/authentication/README.md rename to docs/admin/authentication/README.md index 835abc06..67bfbc25 100644 --- a/docs/authentication/README.md +++ b/docs/admin/authentication/README.md @@ -92,7 +92,7 @@ After configuring OIDC, users who log in will be authenticated but will not have 3. Assign appropriate role 4. User logs in again and will have assigned permissions -See [User Management](../legacy/user-management.md) for detailed instructions. +See [User Management](../../legacy/user-management.md) for detailed instructions. ## Troubleshooting @@ -120,5 +120,5 @@ See [User Management](../legacy/user-management.md) for detailed instructions. ## Additional Resources -- [Authentication Technical Details](../legacy/authentication.md) -- [User Management Guide](../legacy/user-management.md) +- [Authentication Technical Details](../../legacy/authentication.md) +- [User Management Guide](../../legacy/user-management.md) diff --git a/docs/authentication/authentik.md b/docs/admin/authentication/authentik.md similarity index 100% rename from docs/authentication/authentik.md rename to docs/admin/authentication/authentik.md diff --git a/docs/authentication/configuration.md b/docs/admin/authentication/configuration.md similarity index 100% rename from docs/authentication/configuration.md rename to docs/admin/authentication/configuration.md diff --git a/docs/authentication/keycloak.md b/docs/admin/authentication/keycloak.md similarity index 100% rename from docs/authentication/keycloak.md rename to docs/admin/authentication/keycloak.md diff --git a/docs/authentication/okta.md b/docs/admin/authentication/okta.md similarity index 100% rename from docs/authentication/okta.md rename to docs/admin/authentication/okta.md diff --git a/docs/authentication/testing-verification.md b/docs/admin/authentication/testing-verification.md similarity index 100% rename from docs/authentication/testing-verification.md rename to docs/admin/authentication/testing-verification.md diff --git a/docs/configuration.md b/docs/admin/configuration.md similarity index 100% rename from docs/configuration.md rename to docs/admin/configuration.md diff --git a/docs/CROSS_SERVICE_READS_PATTERN.md b/docs/developer/cross-service-reads-pattern.md similarity index 99% rename from docs/CROSS_SERVICE_READS_PATTERN.md rename to docs/developer/cross-service-reads-pattern.md index e7b256c1..a487faf6 100644 --- a/docs/CROSS_SERVICE_READS_PATTERN.md +++ b/docs/developer/cross-service-reads-pattern.md @@ -200,7 +200,7 @@ const metadata = await EventBus.emitAndWait('analytic::metadata-requested', { ### For Documentation -Update [EVENT_BUS_ARCHITECTURE.md](EVENT_BUS_ARCHITECTURE.md) to clarify: +Update [event-bus-architecture.md](event-bus-architecture.md) to clarify: ```markdown ### Cross-Service Communication Rules diff --git a/docs/data-model.md b/docs/developer/data-model.md similarity index 100% rename from docs/data-model.md rename to docs/developer/data-model.md diff --git a/docs/EVENT_BUS_ARCHITECTURE.md b/docs/developer/event-bus-architecture.md similarity index 100% rename from docs/EVENT_BUS_ARCHITECTURE.md rename to docs/developer/event-bus-architecture.md diff --git a/docs/IMPLEMENTATION_APPROACH.md b/docs/developer/implementation-approach.md similarity index 100% rename from docs/IMPLEMENTATION_APPROACH.md rename to docs/developer/implementation-approach.md diff --git a/docs/LIFECYCLE_HOOKS_GUIDE.md b/docs/developer/lifecycle-hooks-guide.md similarity index 100% rename from docs/LIFECYCLE_HOOKS_GUIDE.md rename to docs/developer/lifecycle-hooks-guide.md diff --git a/docs/COLLECTIONS_V2/06_ENTITIES.md b/docs/developer/release-tracks/entities.md similarity index 100% rename from docs/COLLECTIONS_V2/06_ENTITIES.md rename to docs/developer/release-tracks/entities.md diff --git a/docs/COLLECTIONS_V2/99_ERROR_HANDLING.md b/docs/developer/release-tracks/error-handling.md similarity index 100% rename from docs/COLLECTIONS_V2/99_ERROR_HANDLING.md rename to docs/developer/release-tracks/error-handling.md diff --git a/docs/COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md b/docs/developer/release-tracks/implementation-notes.md similarity index 100% rename from docs/COLLECTIONS_V2/99_IMPLEMENTATION_NOTES.md rename to docs/developer/release-tracks/implementation-notes.md diff --git a/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md b/docs/developer/release-tracks/member-sync-strategies.md similarity index 98% rename from docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md rename to docs/developer/release-tracks/member-sync-strategies.md index 1d3c7c81..267dc305 100644 --- a/docs/COLLECTIONS_V2/08_MEMBER_SYNC_STRATEGIES.md +++ b/docs/developer/release-tracks/member-sync-strategies.md @@ -5,10 +5,10 @@ This document describes the **Member Sync Strategy** system, which governs how release tracks respond when new revisions of member objects are created. This feature addresses a critical gap in the release track workflow: ensuring that future revisions of already-released objects are automatically queued for subsequent releases. **Related Documentation:** -- [00_API_REFERENCE.md](./00_API_REFERENCE.md) - Complete API reference -- [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) - Core terminology (candidates, staged, members) -- [05_RELEASE_WORKFLOW.md](./05_RELEASE_WORKFLOW.md) - Workflow states and promotion -- [06_ENTITIES.md](./06_ENTITIES.md) - Database schemas and data models +- [api-reference.md](../../user/release-tracks/api-reference.md) - Complete API reference +- [terminology.md](../../user/release-tracks/terminology.md) - Core terminology (candidates, staged, members) +- [release-workflow.md](../../user/release-tracks/release-workflow.md) - Workflow states and promotion +- [entities.md](./entities.md) - Database schemas and data models --- diff --git a/docs/SERVICE_EXCEPTION_MIDDLEWARE.md b/docs/developer/service-exception-middleware.md similarity index 100% rename from docs/SERVICE_EXCEPTION_MIDDLEWARE.md rename to docs/developer/service-exception-middleware.md diff --git a/docs/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md b/docs/developer/stix-versioning-and-embedded-relationships.md similarity index 99% rename from docs/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md rename to docs/developer/stix-versioning-and-embedded-relationships.md index c39ca9e4..15541ddf 100644 --- a/docs/STIX_VERSIONING_AND_EMBEDDED_RELATIONSHIPS.md +++ b/docs/developer/stix-versioning-and-embedded-relationships.md @@ -608,6 +608,6 @@ If you find yourself frequently needing complete historical relationship graphs, ## Related Documentation -- [EVENT_BUS_ARCHITECTURE.md](EVENT_BUS_ARCHITECTURE.md) - Event-driven architecture patterns -- [LIFECYCLE_HOOKS_GUIDE.md](LIFECYCLE_HOOKS_GUIDE.md) - Service lifecycle hooks +- [event-bus-architecture.md](event-bus-architecture.md) - Event-driven architecture patterns +- [lifecycle-hooks-guide.md](lifecycle-hooks-guide.md) - Service lifecycle hooks - [STIX 2.1 Specification](https://docs.oasis-open.org/cti/stix/v2.1/stix-v2.1.html) - Official STIX standard diff --git a/docs/TASK_SCHEDULER.md b/docs/developer/task-scheduler.md similarity index 100% rename from docs/TASK_SCHEDULER.md rename to docs/developer/task-scheduler.md diff --git a/docs/COLLECTIONS_V2/00_API_REFERENCE.md b/docs/user/release-tracks/api-reference.md similarity index 96% rename from docs/COLLECTIONS_V2/00_API_REFERENCE.md rename to docs/user/release-tracks/api-reference.md index 2a82ddab..08601f2a 100644 --- a/docs/COLLECTIONS_V2/00_API_REFERENCE.md +++ b/docs/user/release-tracks/api-reference.md @@ -5,14 +5,14 @@ This document provides the complete API reference for Release Tracks V2 (formerly "Collections V2"). **Related Documentation:** -- [01_SUMMARY.md](./01_SUMMARY.md) - High-level design summary and problem statement -- [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) - Complete terminology guide -- [03_VERSIONING.md](./03_VERSIONING.md) - Versioning and release process -- [04_VIRTUAL_TRACKS.md](./04_VIRTUAL_TRACKS.md) - Virtual release tracks (aggregations) -- [05_RELEASE_WORKFLOW.md](./05_RELEASE_WORKFLOW.md) - Workflow integration and candidacy -- [06_ENTITIES.md](./06_ENTITIES.md) - Database schemas and data models -- [07_OUTPUT_FORMATS.md](./07_OUTPUT_FORMATS.md) - Output format specifications -- [08_MEMBER_SYNC_STRATEGIES.md](./08_MEMBER_SYNC_STRATEGIES.md) - Automatic tracking of member object revisions +- [summary.md](./summary.md) - High-level design summary and problem statement +- [terminology.md](./terminology.md) - Complete terminology guide +- [versioning.md](./versioning.md) - Versioning and release process +- [virtual-tracks.md](./virtual-tracks.md) - Virtual release tracks (aggregations) +- [release-workflow.md](./release-workflow.md) - Workflow integration and candidacy +- [entities.md](../../developer/release-tracks/entities.md) - Database schemas and data models +- [output-formats.md](./output-formats.md) - Output format specifications +- [member-sync-strategies.md](../../developer/release-tracks/member-sync-strategies.md) - Automatic tracking of member object revisions **Quick Navigation:** - [Ephemeral Release Tracks](#ephemeral-release-tracks) @@ -308,7 +308,7 @@ Creates new snapshot with updated metadata. POST /api/release-tracks/:id/contents ``` -Creates new snapshot with updated member objects. **This is intended for retroactive hotfixes only.** The main workflow for enrolling new member objects into `x_mitre_contents` is through the candidate-staging promotion cycle described in [03_VERSIONING.md](./03_VERSIONING.md). +Creates new snapshot with updated member objects. **This is intended for retroactive hotfixes only.** The main workflow for enrolling new member objects into `x_mitre_contents` is through the candidate-staging promotion cycle described in [versioning.md](./versioning.md). **Request Body:** ```json @@ -800,7 +800,7 @@ Virtual release tracks are computed aggregations of other release tracks. Unlike 2. `specific_version` - Pin to a specific semantic version (e.g., "5.0") 3. `specific_snapshot` - Pin to a specific snapshot by timestamp -See [04_VIRTUAL_TRACKS.md](./04_VIRTUAL_TRACKS.md) for complete documentation. +See [virtual-tracks.md](./virtual-tracks.md) for complete documentation. ### Create Virtual Track diff --git a/docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md b/docs/user/release-tracks/output-formats.md similarity index 100% rename from docs/COLLECTIONS_V2/07_OUTPUT_FORMATS.md rename to docs/user/release-tracks/output-formats.md diff --git a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md b/docs/user/release-tracks/release-workflow.md similarity index 98% rename from docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md rename to docs/user/release-tracks/release-workflow.md index d1bbd10f..c39faa04 100644 --- a/docs/COLLECTIONS_V2/05_RELEASE_WORKFLOW.md +++ b/docs/user/release-tracks/release-workflow.md @@ -6,10 +6,10 @@ This document describes how object workflow states integrate with the release tr **Key Design Decision:** This system uses **release track-centric status with version pinning** to solve the "STIX freeze" problem. Each release track tracks its own workflow status for objects and pins to specific object versions, allowing the same object to be in different states across different release tracks and enabling work on future releases while current releases are frozen. -**Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) for the complete terminology guide. +**Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [terminology.md](./terminology.md) for the complete terminology guide. **Related Documentation:** -- [08_MEMBER_SYNC_STRATEGIES.md](./08_MEMBER_SYNC_STRATEGIES.md) - Automatic tracking of new member object revisions +- [member-sync-strategies.md](../../developer/release-tracks/member-sync-strategies.md) - Automatic tracking of new member object revisions ## Core Concepts @@ -345,7 +345,7 @@ Keep whichever version has the newer `modified` timestamp. ##### 4. `abort` (Tagging/Release Operations Only) -[](./05_RELEASE_WORKFLOW.md#4-abort-taggingrelease-operations-only) +[](./release-workflow.md#4-abort-taggingrelease-operations-only) **Only available for `staged_to_members` during tagging/release operations.** If a conflict occurs during a tagging/release operation (`POST /api/release-tracks/:id/bump`), reject and abort the entire release. The snapshot will NOT be tagged, and no immutable snapshot will be created. @@ -992,7 +992,7 @@ Set up event handlers for: Virtual release tracks follow a different workflow since they don't manage objects directly. Instead, they aggregate content from component tracks. -See [04_VIRTUAL_TRACKS.md](04_VIRTUAL_TRACKS.md) for complete virtual track documentation. +See [virtual-tracks.md](virtual-tracks.md) for complete virtual track documentation. ### Basic Virtual Track Workflow diff --git a/docs/COLLECTIONS_V2/01_SUMMARY.md b/docs/user/release-tracks/summary.md similarity index 94% rename from docs/COLLECTIONS_V2/01_SUMMARY.md rename to docs/user/release-tracks/summary.md index 800f93ca..dd2c4717 100644 --- a/docs/COLLECTIONS_V2/01_SUMMARY.md +++ b/docs/user/release-tracks/summary.md @@ -4,7 +4,7 @@ This document provides a high-level summary of the Release Tracks API refactor (a.k.a. "Collections V2"), tying together the versioning system and workflow integration. -**Note on Terminology:** We're replacing the overloaded term "collection" with **release track** to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](./02_TERMINOLOGY.md) for complete terminology guide. +**Note on Terminology:** We're replacing the overloaded term "collection" with **release track** to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [terminology.md](./terminology.md) for complete terminology guide. ## Problem Statement @@ -39,7 +39,7 @@ The Release Tracks API supports two types of release tracks: Teams can organize objects into modular standard tracks by type (e.g., one track for Groups, one for Techniques), each with its own release cadence. Then create virtual tracks that aggregate these into domain-specific releases (e.g., Enterprise ATT&CK = Groups + Techniques + Software) without duplicating object tracking. -See [04_VIRTUAL_TRACKS.md](./04_VIRTUAL_TRACKS.md) for complete virtual track documentation. +See [virtual-tracks.md](./virtual-tracks.md) for complete virtual track documentation. ### 1. Unified API Structure @@ -55,7 +55,7 @@ GET /api/collections/:id (retrieve) **New API V2 (partial preview):** -The new API is still a work in progress. The source of truth is located in [00_API_REFERENCE.md](./00_API_REFERENCE.md). The following is a preview. If there are any discrepencies between what is shown here and what is shown in [00_API_REFERENCE.md](./00_API_REFERENCE.md), defer to the latter. +The new API is still a work in progress. The source of truth is located in [api-reference.md](./api-reference.md). The following is a preview. If there are any discrepencies between what is shown here and what is shown in [api-reference.md](./api-reference.md), defer to the latter. ``` # Ephemeral bundles (stateless) GET /api/release-tracks/ephemeral/:domain @@ -107,7 +107,7 @@ We use the preexisting object workflow statuses, `work-in-progress`, `awaiting-r There are three types of membership "standings": 1. **Candidate**: When an object is first added to a release track, is it considered a candidate. It does not have full membership yet; if the snapshot were to be tagged and released right now, candidates would not be included. - 2. **Staged**: Once a candidate's workflow status meets the release track's ["candidacy threshold"](./4_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in the resultant bundle's `x_mitre_contents`. + 2. **Staged**: Once a candidate's workflow status meets the release track's ["candidacy threshold"](./release-workflow.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in the resultant bundle's `x_mitre_contents`. 3. **Member**: Objects are considered "members" if they are "cooked" into the `x_mitre_contents` array of the current snapshot. These are considered already released. This presents a tenable solution to the classic "STIX freeze" dilemma wherein editors cannot begin working on the next-*next* (e.g., v20) release until all objects in the next (e.g., v19) release have been released. Staged objects are locked in for the imminent release, but editors are free to continue iterating on future object changes and can queue them up as candidates without affecting the permutation that has already been staged for the imminent release. diff --git a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md b/docs/user/release-tracks/terminology.md similarity index 98% rename from docs/COLLECTIONS_V2/02_TERMINOLOGY.md rename to docs/user/release-tracks/terminology.md index 43a726c4..14234894 100644 --- a/docs/COLLECTIONS_V2/02_TERMINOLOGY.md +++ b/docs/user/release-tracks/terminology.md @@ -33,7 +33,7 @@ A **release track** (RT) is a series/chain (linked list) of **snapshots**, where - The release track provides version control and release management for curated sets of STIX objects **Characteristics:** -- Has a unique identifier (e.g., `release-track--uuid`) (see [naming conventions](./06_ENTITIES.md#naming-conventions) for details) +- Has a unique identifier (e.g., `release-track--uuid`) (see [naming conventions](../../developer/release-tracks/entities.md#naming-conventions) for details) - Contains a chronological history of all changes - Supports Git-inspired versioning workflow @@ -196,12 +196,12 @@ Standard release tracks use three tiers to manage the object lifecycle from deve **Definition:** Objects that have been reviewed (in this release track) and are ready for the next tagged release. **Characteristics:** -- Once a candidate's workflow status meets the release track's ["candidacy threshold"](./05_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `members`. +- Once a candidate's workflow status meets the release track's ["candidacy threshold"](./release-workflow.md#candidacy-threshold-configuration) criteria, it will automatically become staged. Once the snapshot is tagged/released, staged objects will be included in `members`. When the release is exported as a `bundle`, all `members` will be included in the resultant bundle's `x_mitre_contents` array. - Each `staged` entry includes a version pin (`object_modified` timestamp), which can either equal an ISO 8601 timestamp (designating a specific object version) or `"latest"` (designating a dynamic reference to the latest permutation of the relevant object) -- Auto-promoted from candidates when objects meet the [candidacy threshold](./05_RELEASE_WORKFLOW.md#candidacy-threshold-configuration) +- Auto-promoted from candidates when objects meet the [candidacy threshold](./release-workflow.md#candidacy-threshold-configuration) - Moved to member objects tier (`members`) when the snapshot is tagged - NOT included in published STIX bundles until the snapshot is tagged diff --git a/docs/COLLECTIONS_V2/03_VERSIONING.md b/docs/user/release-tracks/versioning.md similarity index 97% rename from docs/COLLECTIONS_V2/03_VERSIONING.md rename to docs/user/release-tracks/versioning.md index b3f3447b..2c13c10b 100644 --- a/docs/COLLECTIONS_V2/03_VERSIONING.md +++ b/docs/user/release-tracks/versioning.md @@ -9,7 +9,7 @@ The Release Tracks API uses a Git-inspired versioning strategy that separates tw This approach allows continuous development while providing stable, versioned releases for publication. -**Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [02_TERMINOLOGY.md](02_TERMINOLOGY.md) for the complete terminology guide. +**Note on Terminology:** We use **release track** instead of "collection" to avoid confusion with TAXII collections, MongoDB collections, STIX bundles, and `x-mitre-collection` SDOs. See [terminology.md](terminology.md) for the complete terminology guide. ## Core Concepts diff --git a/docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md b/docs/user/release-tracks/virtual-tracks.md similarity index 100% rename from docs/COLLECTIONS_V2/04_VIRTUAL_TRACKS.md rename to docs/user/release-tracks/virtual-tracks.md diff --git a/docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md b/docs/user/release-tracks/workflow-examples.md similarity index 100% rename from docs/COLLECTIONS_V2/99_WORKFLOW_EXAMPLES.md rename to docs/user/release-tracks/workflow-examples.md diff --git a/docs/revoke-workflow.md b/docs/user/revoke-workflow.md similarity index 100% rename from docs/revoke-workflow.md rename to docs/user/revoke-workflow.md From 79ee10e00ff903278351c90252882fa38fcbb502 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:13:56 -0400 Subject: [PATCH 238/370] docs: fix typo in revoke-workflow documentation --- docs/user/revoke-workflow.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user/revoke-workflow.md b/docs/user/revoke-workflow.md index 91b463ab..7dedddcd 100644 --- a/docs/user/revoke-workflow.md +++ b/docs/user/revoke-workflow.md @@ -9,9 +9,11 @@ Formerly, this workflow was orchestrated by the frontend, which made multiple AP To revoke an object, send a POST request to the following endpoint: ``` -POST /api/objects/:stixId/revoke +POST /api/:type/:stixId/revoke ``` -Where `:stixId` is the STIX ID of the object you want to revoke. +Where `:stixId` is the STIX ID of the object you want to revoke and `:type` is the type of the object. + +e.g., `POST /api/attack-patterns/attack-pattern--00290ac5-551e-44aa-bbd8-c4b913488a6c/revoke` **Request Body**: From 6535b2b1a1b38d697e708cafc3f345d5ba3f8072 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:39:22 -0400 Subject: [PATCH 239/370] fix: stop throwing assertion error when analytic refs are omitted - Previously, when posting a detection strategy, analytic refs were required - If omitted, an assertion error would raise and would not be caught, resulting in the server crashing - The assertUnique function is now tolerant of undefined/null analytic_ref lists - This is preferable because WIP DETs are permitted to leave this field empty - Additionally, the true negative condition now raises BadlyFormattedParameterError which is caught --- app/lib/assertions.js | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/app/lib/assertions.js b/app/lib/assertions.js index d366b347..e7e6fb9e 100644 --- a/app/lib/assertions.js +++ b/app/lib/assertions.js @@ -1,6 +1,6 @@ 'use strict'; -const assert = require('assert'); +const { BadlyFormattedParameterError } = require('../exceptions'); /** * Service-layer assertion utilities @@ -9,6 +9,9 @@ const assert = require('assert'); * Unlike middleware validation (which validates user input), these assertions check for programming * errors and data integrity issues that should never occur in normal operation. * + * These assertions throw BadlyFormattedParameterError (resulting in 400 responses for direct API calls) + * and are caught and categorized as validation errors during bulk import operations. + * * Usage: * ``` * const assertions = require('./lib/assertions'); @@ -19,21 +22,32 @@ const assert = require('assert'); /** * Assert that an array contains only unique values * - * @param {Array} array - The array to check for uniqueness + * @param {Array|undefined|null} array - The array to check for uniqueness (undefined/null are allowed and skip validation) * @param {string} fieldName - Name of the field being checked (for error messages) * @param {object} context - Additional context to include in error message (e.g., { stixId: '...' }) - * @throws {AssertionError} If array contains duplicate values + * @throws {BadlyFormattedParameterError} If array is provided but not an array type, or contains duplicate values * * @example * assertUnique(['a', 'b', 'c'], 'analytic_refs', { stixId: 'detection-strategy--123' }); * // Passes * + * assertUnique(undefined, 'analytic_refs', { stixId: 'detection-strategy--123' }); + * // Passes (undefined is allowed - field is optional) + * * assertUnique(['a', 'b', 'a'], 'analytic_refs', { stixId: 'detection-strategy--123' }); - * // Throws: AssertionError: analytic_refs must contain unique values. Found duplicates in detection-strategy--123 + * // Throws: BadlyFormattedParameterError: analytic_refs must contain unique values. Found duplicates in detection-strategy--123 */ function assertUnique(array, fieldName, context = {}) { + // Allow undefined/null - the field may be optional + if (array === undefined || array === null) { + return; + } + if (!Array.isArray(array)) { - assert.fail(`${fieldName} must be an array, got ${typeof array}`); + throw new BadlyFormattedParameterError({ + parameterName: fieldName, + message: `${fieldName} must be an array, got ${typeof array}`, + }); } if (array.length === 0) { @@ -43,11 +57,12 @@ function assertUnique(array, fieldName, context = {}) { const uniqueValues = new Set(array); const contextStr = context.stixId ? ` in ${context.stixId}` : ''; - assert.strictEqual( - uniqueValues.size, - array.length, - `${fieldName} must contain unique values. Found duplicates${contextStr}`, - ); + if (uniqueValues.size !== array.length) { + throw new BadlyFormattedParameterError({ + parameterName: fieldName, + message: `${fieldName} must contain unique values. Found duplicates${contextStr}`, + }); + } } module.exports = { From ceaa2165ec2fbdc4e912624a21d325f9fa16bf75 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:53:00 -0400 Subject: [PATCH 240/370] fix: stop requiring first + last seen fields for campaigns in openapi spec We don't need to explicitly require these propeties. The ADM validation will handle this appropriately based on the object status. --- app/api/definitions/components/campaigns.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/api/definitions/components/campaigns.yml b/app/api/definitions/components/campaigns.yml index fb6f3d63..bc4bf904 100644 --- a/app/api/definitions/components/campaigns.yml +++ b/app/api/definitions/components/campaigns.yml @@ -17,10 +17,6 @@ components: - type: object required: - name - - first_seen - - last_seen - - x_mitre_first_seen_citation - - x_mitre_last_seen_citation properties: # campaign specific properties name: From 438e2b429820f0b94572a6152dae9d0ac6ecda25 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:56:57 -0400 Subject: [PATCH 241/370] feat: make spec_version/created/modified optional; server sets defaults when omitted , , and are no longer required in request bodies; the server now generates/sets them if not provided (preparing to eventually disallow client-supplied values). --- app/api/definitions/components/stix-common.yml | 3 --- app/services/meta-classes/base.service.js | 13 +++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/api/definitions/components/stix-common.yml b/app/api/definitions/components/stix-common.yml index 96710d19..72b8e24f 100644 --- a/app/api/definitions/components/stix-common.yml +++ b/app/api/definitions/components/stix-common.yml @@ -4,9 +4,6 @@ components: type: object required: - type - - spec_version - - created - - modified properties: # STIX common properties type: diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 578f4a83..43e5ad68 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -514,10 +514,23 @@ class BaseService extends ServiceWithHooks { if (!data.stix.id) { data.stix.id = `${data.stix.type}--${uuid.v4()}`; } + if (!data.stix.created) { + data.stix.created = new Date().toISOString(); + } data.stix.created_by_ref = organizationIdentityRef; data.stix.x_mitre_modified_by_ref = organizationIdentityRef; } + // Set modified timestamp if not set by client — set for both new and existing objects + if (!data.stix.modified) { + data.stix.modified = new Date().toISOString(); + } + + // Set default spec_version if not provided by client + if (!data.stix.spec_version) { + data.stix.spec_version = '2.1'; + } + // 3b. Metadata fields if (options.userAccountId) { data.workspace.workflow.created_by_user_account = options.userAccountId; From bf4cb46017c2ce2865706f34ba0faaa115122674 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:18:02 -0400 Subject: [PATCH 242/370] test: stop requiring spec_version in group-related tests --- app/tests/api/groups/groups-input-validation.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/api/groups/groups-input-validation.spec.js b/app/tests/api/groups/groups-input-validation.spec.js index c39efa60..f3bbbd25 100644 --- a/app/tests/api/groups/groups-input-validation.spec.js +++ b/app/tests/api/groups/groups-input-validation.spec.js @@ -160,7 +160,7 @@ describe('Groups API Input Validation', function () { }); executeTests(() => app, 'stix.type', { required: true }); - executeTests(() => app, 'stix.spec_version', { required: true }); + executeTests(() => app, 'stix.spec_version', { required: false }); executeTests(() => app, 'stix.name', { required: true }); executeTests(() => app, 'stix.description'); executeTests(() => app, 'stix.x_mitre_modified_by_ref'); From 40e87afe976dbdbb8293eb0536d423a7bdadaac1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:17:35 -0400 Subject: [PATCH 243/370] feat: define new endpoints for converting technique in openapi spec docs --- app/api/definitions/openapi.yml | 6 ++ .../definitions/paths/techniques-paths.yml | 82 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 6d7d9c2e..65b08ff6 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -95,6 +95,12 @@ paths: /api/techniques/{stixId}/modified/{modified}: $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1modified~1{modified}' + /api/techniques/{stixId}/convert-to-subtechnique: + $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1convert-to-subtechnique' + + /api/techniques/{stixId}/convert-to-technique: + $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1convert-to-technique' + /api/techniques/{stixId}/revoke: $ref: 'paths/techniques-paths.yml#/paths/~1api~1techniques~1{stixId}~1revoke' diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index 92b21ad7..68caec30 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -367,6 +367,88 @@ paths: '404': description: 'A technique with the requested STIX id and modified date was not found.' + /api/techniques/{stixId}/convert-to-subtechnique: + post: + summary: 'Convert a technique to a subtechnique' + operationId: 'technique-convert-to-subtechnique' + description: | + Converts the technique identified by the stixId path parameter into a subtechnique + of the specified parent technique. This operation: + - Generates a new subtechnique-format ATT&CK ID (e.g., T1234.001) + - Sets `x_mitre_is_subtechnique` to `true` + - Rebuilds the ATT&CK external reference with the new ID and URL + - Persists the result as a new version of the object + + The technique must not already be a subtechnique or revoked. + tags: + - 'Techniques' + parameters: + - name: stixId + in: path + description: 'STIX id of the technique to convert' + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - parentTechniqueAttackId + properties: + parentTechniqueAttackId: + type: string + pattern: '^T\d{4}$' + description: 'ATT&CK ID of the parent technique (e.g., T1234)' + example: 'T1234' + responses: + '200': + description: 'The technique was successfully converted to a subtechnique.' + content: + application/json: + schema: + $ref: '../components/techniques.yml#/components/schemas/technique' + '400': + description: 'Invalid request. The technique is already a subtechnique, is revoked, or the parent ID is invalid.' + '404': + description: 'The technique was not found.' + + /api/techniques/{stixId}/convert-to-technique: + post: + summary: 'Convert a subtechnique to a technique' + operationId: 'technique-convert-to-technique' + description: | + Converts the subtechnique identified by the stixId path parameter into a top-level technique. + This operation: + - Generates a new technique-format ATT&CK ID (e.g., T1235) + - Sets `x_mitre_is_subtechnique` to `false` + - Rebuilds the ATT&CK external reference with the new ID and URL + - Persists the result as a new version of the object + + The technique must currently be a subtechnique and must not be revoked. + tags: + - 'Techniques' + parameters: + - name: stixId + in: path + description: 'STIX id of the subtechnique to convert' + required: true + schema: + type: string + responses: + '200': + description: 'The subtechnique was successfully converted to a technique.' + content: + application/json: + schema: + $ref: '../components/techniques.yml#/components/schemas/technique' + '400': + description: 'Invalid request. The object is not a subtechnique or is revoked.' + '404': + description: 'The technique was not found.' + /api/techniques/{stixId}/revoke: post: summary: 'Revoke a technique' From 01141afd5e800ce9a15d75e26f8d0e145499b46d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:19:40 -0400 Subject: [PATCH 244/370] feat: implement service methods for converting technique to sub and vice versa --- app/services/stix/relationships-service.js | 72 +++++++ app/services/stix/techniques-service.js | 207 ++++++++++++++++++++- 2 files changed, 278 insertions(+), 1 deletion(-) diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index 68b356c6..8aed7878 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -3,6 +3,9 @@ const { BaseService } = require('../meta-classes'); const relationshipsRepository = require('../../repository/relationships-repository'); const { Relationship: RelationshipType } = require('../../lib/types'); +const EventBus = require('../../lib/event-bus'); +const EventConstants = require('../../lib/event-constants'); +const logger = require('../../lib/logger'); // Map STIX types to ATT&CK types const objectTypeMap = new Map([ @@ -20,6 +23,73 @@ const objectTypeMap = new Map([ ]); class RelationshipsService extends BaseService { + /** + * Initialize event listeners. + * Called once on module load. + */ + static initializeEventListeners() { + EventBus.on( + EventConstants.SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE, + RelationshipsService.handleSubtechniqueConvertedToTechnique.bind(RelationshipsService), + ); + + logger.info('RelationshipsService: Event listeners initialized'); + } + + /** + * Deprecate subtechnique-of SROs when a subtechnique is converted to a technique. + * + * Creates a new version of each active subtechnique-of relationship where + * source_ref = the converted object, setting x_mitre_deprecated = true. + * + * @param {Object} payload - Event payload + * @param {string} payload.stixId - STIX ID of the converted subtechnique + */ + static async handleSubtechniqueConvertedToTechnique(payload) { + const { stixId } = payload; + + logger.info(`RelationshipsService: Deprecating subtechnique-of relationships for ${stixId}`); + + try { + const subtechniqueOfRels = await relationshipsRepository.retrieveAll({ + sourceRef: stixId, + relationshipType: 'subtechnique-of', + versions: 'latest', + includeRevoked: false, + includeDeprecated: false, + }); + + let deprecatedCount = 0; + for (const rel of subtechniqueOfRels) { + try { + const deprecatedVersion = rel.toObject ? rel.toObject() : { ...rel }; + delete deprecatedVersion._id; + delete deprecatedVersion.__v; + delete deprecatedVersion.__t; + + deprecatedVersion.stix.x_mitre_deprecated = true; + deprecatedVersion.stix.modified = new Date().toISOString(); + + await relationshipsRepository.save(deprecatedVersion); + deprecatedCount++; + } catch (error) { + logger.error( + `RelationshipsService: Error deprecating relationship ${rel.stix?.id}: ${error.message}`, + ); + } + } + + logger.info( + `RelationshipsService: Deprecated ${deprecatedCount}/${subtechniqueOfRels.length} subtechnique-of relationship(s) for ${stixId}`, + ); + } catch (error) { + logger.error( + `RelationshipsService: Error handling subtechnique-to-technique conversion for ${stixId}:`, + error, + ); + } + } + async retrieveAll(options) { let results = await this.repository.retrieveAll(options); @@ -100,6 +170,8 @@ class RelationshipsService extends BaseService { } } +RelationshipsService.initializeEventListeners(); + // Default export module.exports.RelationshipsService = RelationshipsService; diff --git a/app/services/stix/techniques-service.js b/app/services/stix/techniques-service.js index 327db2b6..82642e0d 100644 --- a/app/services/stix/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -5,7 +5,17 @@ const { BaseService } = require('../meta-classes'); const techniquesRepository = require('../../repository/techniques-repository'); const { Technique: TechniqueType } = require('../../lib/types'); -const { BadlyFormattedParameterError, MissingParameterError } = require('../../exceptions'); +const { + BadlyFormattedParameterError, + BadRequestError, + MissingParameterError, + NotFoundError, +} = require('../../exceptions'); +const attackIdGenerator = require('../../lib/attack-id-generator'); +const { + buildAttackExternalReference, + removeAttackExternalReferences, +} = require('../../lib/external-reference-builder'); const EventBus = require('../../lib/event-bus'); const EventConstants = require('../../lib/event-constants'); const logger = require('../../lib/logger'); @@ -158,6 +168,201 @@ class TechniquesService extends BaseService { return data.slice(startPos, endPos); } + // ============================ + // Subtechnique Conversion + // ============================ + + /** + * Convert a technique to a subtechnique. + * + * Generates a new subtechnique-format ATT&CK ID (e.g., T1234.001) under the + * specified parent, updates x_mitre_is_subtechnique, rebuilds the ATT&CK + * external reference, and persists the result as a new version. + * + * @param {string} stixId - STIX ID of the technique to convert + * @param {Object} data - Request body + * @param {string} data.parentTechniqueAttackId - Parent technique ATT&CK ID (e.g., T1234) + * @param {Object} [options] - Options + * @param {string} [options.userAccountId] - Authenticated user's account ID + * @returns {Object} The newly created subtechnique version + */ + async convertToSubtechnique(stixId, data, options = {}) { + // Lazy-load to avoid circular dependency + const relationshipsRepository = require('../../repository/relationships-repository'); + + if (!stixId) { + throw new MissingParameterError('stixId'); + } + if (!data?.parentTechniqueAttackId) { + throw new MissingParameterError('parentTechniqueAttackId'); + } + if (!/^T\d{4}$/.test(data.parentTechniqueAttackId)) { + throw new BadRequestError({ + details: `Invalid parent technique ATT&CK ID format: ${data.parentTechniqueAttackId}. Must be T####.`, + }); + } + + const technique = await this.repository.retrieveLatestByStixId(stixId); + if (!technique) { + throw new NotFoundError({ details: `Technique with stixId ${stixId} not found` }); + } + if (technique.stix.x_mitre_is_subtechnique === true) { + throw new BadRequestError({ + details: `Technique ${stixId} is already a subtechnique`, + }); + } + if (technique.stix.revoked === true) { + throw new BadRequestError({ + details: `Cannot convert a revoked technique`, + }); + } + + // Check if this technique has child subtechniques (via subtechnique-of SROs). + // Cross-service READ is permitted per architecture guidelines. + // If children exist, block the conversion — the user must rehome them first. + const childRelationships = await relationshipsRepository.retrieveAll({ + targetRef: stixId, + relationshipType: 'subtechnique-of', + versions: 'latest', + includeRevoked: false, + includeDeprecated: false, + }); + if (childRelationships.length > 0) { + throw new BadRequestError({ + details: + `Technique ${stixId} has ${childRelationships.length} subtechnique(s). ` + + `Rehome or remove the subtechnique-of relationships before converting this technique to a subtechnique.`, + }); + } + + // Generate new subtechnique ATT&CK ID + const newAttackId = await attackIdGenerator.generateAttackId( + 'attack-pattern', + this.repository, + true, + data.parentTechniqueAttackId, + ); + + // Build new version + const newVersion = technique.toObject ? technique.toObject() : { ...technique }; + delete newVersion._id; + delete newVersion.__v; + delete newVersion.__t; + + newVersion.stix.x_mitre_is_subtechnique = true; + newVersion.stix.modified = new Date().toISOString(); + newVersion.workspace = newVersion.workspace || {}; + newVersion.workspace.attack_id = newAttackId; + + // Rebuild external references: replace ATT&CK ref with the new one + const userRefs = removeAttackExternalReferences(newVersion.stix.external_references); + const newAttackRef = buildAttackExternalReference(newAttackId, 'attack-pattern', { + isSubtechnique: true, + }); + newVersion.stix.external_references = newAttackRef ? [newAttackRef, ...userRefs] : userRefs; + + if (options.userAccountId) { + newVersion.workspace.workflow = newVersion.workspace.workflow || {}; + newVersion.workspace.workflow.created_by_user_account = options.userAccountId; + } + + const savedDocument = await this.repository.save(newVersion); + + logger.info( + `Converted technique ${stixId} to subtechnique: ${technique.workspace?.attack_id} -> ${newAttackId}`, + ); + + // Emit domain event for cross-service coordination + await EventBus.emit(EventConstants.TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE, { + stixId, + document: savedDocument.toObject ? savedDocument.toObject() : savedDocument, + previousAttackId: technique.workspace?.attack_id, + newAttackId, + parentTechniqueAttackId: data.parentTechniqueAttackId, + }); + + return savedDocument.toObject ? savedDocument.toObject() : savedDocument; + } + + /** + * Convert a subtechnique to a technique. + * + * Generates a new technique-format ATT&CK ID (e.g., T1235), updates + * x_mitre_is_subtechnique, rebuilds the ATT&CK external reference, and + * persists the result as a new version. + * + * @param {string} stixId - STIX ID of the subtechnique to convert + * @param {Object} [options] - Options + * @param {string} [options.userAccountId] - Authenticated user's account ID + * @returns {Object} The newly created technique version + */ + async convertToTechnique(stixId, options = {}) { + if (!stixId) { + throw new MissingParameterError('stixId'); + } + + const technique = await this.repository.retrieveLatestByStixId(stixId); + if (!technique) { + throw new NotFoundError({ details: `Technique with stixId ${stixId} not found` }); + } + if (technique.stix.x_mitre_is_subtechnique !== true) { + throw new BadRequestError({ + details: `Technique ${stixId} is not a subtechnique`, + }); + } + if (technique.stix.revoked === true) { + throw new BadRequestError({ + details: `Cannot convert a revoked technique`, + }); + } + + // Generate new technique ATT&CK ID + const newAttackId = await attackIdGenerator.generateAttackId( + 'attack-pattern', + this.repository, + false, + ); + + // Build new version + const newVersion = technique.toObject ? technique.toObject() : { ...technique }; + delete newVersion._id; + delete newVersion.__v; + delete newVersion.__t; + + newVersion.stix.x_mitre_is_subtechnique = false; + newVersion.stix.modified = new Date().toISOString(); + newVersion.workspace = newVersion.workspace || {}; + newVersion.workspace.attack_id = newAttackId; + + // Rebuild external references: replace ATT&CK ref with the new one + const userRefs = removeAttackExternalReferences(newVersion.stix.external_references); + const newAttackRef = buildAttackExternalReference(newAttackId, 'attack-pattern', { + isSubtechnique: false, + }); + newVersion.stix.external_references = newAttackRef ? [newAttackRef, ...userRefs] : userRefs; + + if (options.userAccountId) { + newVersion.workspace.workflow = newVersion.workspace.workflow || {}; + newVersion.workspace.workflow.created_by_user_account = options.userAccountId; + } + + const savedDocument = await this.repository.save(newVersion); + + logger.info( + `Converted subtechnique ${stixId} to technique: ${technique.workspace?.attack_id} -> ${newAttackId}`, + ); + + // Emit domain event — RelationshipsService listens to deprecate subtechnique-of SROs + await EventBus.emit(EventConstants.SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE, { + stixId, + document: savedDocument.toObject ? savedDocument.toObject() : savedDocument, + previousAttackId: technique.workspace?.attack_id, + newAttackId, + }); + + return savedDocument.toObject ? savedDocument.toObject() : savedDocument; + } + async retrieveTacticsForTechnique(stixId, modified, options) { // Late binding to avoid circular dependency between modules if (!TechniquesService.tacticsService) { From 0005a79f74dbe6de7967ca7bb1801b55fb9177fb Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:20:26 -0400 Subject: [PATCH 245/370] feat: bind new technique conversion methods to controller endpoints --- app/controllers/techniques-controller.js | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index 7f39fef5..af190521 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -190,6 +190,36 @@ exports.revoke = async function (req, res, next) { } }; +exports.convertToSubtechnique = async function (req, res, next) { + try { + const options = { + userAccountId: req.user?.userAccountId, + }; + const result = await techniquesService.convertToSubtechnique( + req.params.stixId, + req.body, + options, + ); + logger.debug('Success: Converted technique to subtechnique ' + result.stix.id); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; + +exports.convertToTechnique = async function (req, res, next) { + try { + const options = { + userAccountId: req.user?.userAccountId, + }; + const result = await techniquesService.convertToTechnique(req.params.stixId, options); + logger.debug('Success: Converted subtechnique to technique ' + result.stix.id); + return res.status(200).send(result); + } catch (err) { + return next(err); + } +}; + exports.retrieveTacticsForTechnique = async function (req, res) { try { const options = { From ce3617dcd2745536f4eba90812d57d133f88eb22 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:21:35 -0400 Subject: [PATCH 246/370] feat: attach routes to new technique conversion endpoints --- app/routes/techniques-routes.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/routes/techniques-routes.js b/app/routes/techniques-routes.js index 63537881..cf3c32cf 100644 --- a/app/routes/techniques-routes.js +++ b/app/routes/techniques-routes.js @@ -44,6 +44,22 @@ router .route('/techniques/:stixId/revoke') .post(authn.authenticate, authz.requireRole(authz.editorOrHigher), techniquesController.revoke); +router + .route('/techniques/:stixId/convert-to-subtechnique') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + techniquesController.convertToSubtechnique, + ); + +router + .route('/techniques/:stixId/convert-to-technique') + .post( + authn.authenticate, + authz.requireRole(authz.editorOrHigher), + techniquesController.convertToTechnique, + ); + router .route('/techniques/:stixId/modified/:modified/tactics') .get( From ee982c18ff0d74d28bd56831cad8cfd6a0af10e2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:23:17 -0400 Subject: [PATCH 247/370] fix: block from changing x_mitre_is_subtechnique in PUT/update requests --- app/services/meta-classes/base.service.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 4c613b66..3eec82fc 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -691,6 +691,12 @@ class BaseService extends ServiceWithHooks { // Compose server-controlled fields from existing document data.stix.x_mitre_attack_spec_version = document.stix.x_mitre_attack_spec_version; + // Preserve x_mitre_is_subtechnique — changing subtechnique status requires + // the dedicated conversion endpoints, not the generic update path. + if (document.stix.x_mitre_is_subtechnique !== undefined) { + data.stix.x_mitre_is_subtechnique = document.stix.x_mitre_is_subtechnique; + } + if (document.workspace?.attack_id) { data.workspace = data.workspace || {}; data.workspace.attack_id = document.workspace.attack_id; From 2ecbc398a05550adfca99ff485b7ab680685ee48 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:25:18 -0400 Subject: [PATCH 248/370] test: implement tests for evaluating new technique conversion workflows --- .../api/techniques/techniques.convert.spec.js | 518 ++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 app/tests/api/techniques/techniques.convert.spec.js diff --git a/app/tests/api/techniques/techniques.convert.spec.js b/app/tests/api/techniques/techniques.convert.spec.js new file mode 100644 index 00000000..9b31a19e --- /dev/null +++ b/app/tests/api/techniques/techniques.convert.spec.js @@ -0,0 +1,518 @@ +const request = require('supertest'); +const { expect } = require('expect'); + +const database = require('../../../lib/database-in-memory'); +const databaseConfiguration = require('../../../lib/database-configuration'); + +const config = require('../../../config/config'); +const login = require('../../shared/login'); + +const logger = require('../../../lib/logger'); +logger.level = 'debug'; + +const baseTechniqueData = { + workspace: { + workflow: { + state: 'work-in-progress', + }, + }, + stix: { + name: 'convert-test-technique', + type: 'attack-pattern', + description: 'A technique for conversion tests.', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_is_subtechnique: false, + x_mitre_platforms: ['Linux'], + }, +}; + +describe('Techniques Convert API', function () { + let app; + let passportCookie; + + before(async function () { + await database.initializeConnection(); + await databaseConfiguration.checkSystemConfiguration(); + + config.validateRequests.withAttackDataModel = false; + config.validateRequests.withOpenApi = false; + + app = await require('../../../index').initializeApp(); + passportCookie = await login.loginAnonymous(app); + }); + + // ============================================= + // Convert technique → subtechnique + // ============================================= + describe('POST /api/techniques/:stixId/convert-to-subtechnique', function () { + let parentTechnique; + let technique; + + it('creates a parent technique', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'parent-technique', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + parentTechnique = res.body; + expect(parentTechnique.workspace.attack_id).toBeDefined(); + }); + + it('creates a technique to convert', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'technique-to-convert', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + technique = res.body; + expect(technique.stix.x_mitre_is_subtechnique).toBe(false); + }); + + it('converts the technique to a subtechnique', async function () { + const res = await request(app) + .post(`/api/techniques/${technique.stix.id}/convert-to-subtechnique`) + .send({ parentTechniqueAttackId: parentTechnique.workspace.attack_id }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const converted = res.body; + + // Same STIX ID, new version + expect(converted.stix.id).toBe(technique.stix.id); + expect(converted.stix.modified).not.toBe(technique.stix.modified); + + // Subtechnique fields + expect(converted.stix.x_mitre_is_subtechnique).toBe(true); + expect(converted.workspace.attack_id).toMatch( + new RegExp(`^${parentTechnique.workspace.attack_id}\\.\\d{3}$`), + ); + + // External reference updated + const attackRef = converted.stix.external_references.find( + (ref) => ref.source_name === 'mitre-attack', + ); + expect(attackRef).toBeDefined(); + expect(attackRef.external_id).toBe(converted.workspace.attack_id); + expect(attackRef.url).toContain('/techniques/'); + }); + + it('returns 400 when technique is already a subtechnique', async function () { + await request(app) + .post(`/api/techniques/${technique.stix.id}/convert-to-subtechnique`) + .send({ parentTechniqueAttackId: parentTechnique.workspace.attack_id }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + + it('returns 400 when parentTechniqueAttackId is missing', async function () { + await request(app) + .post(`/api/techniques/${technique.stix.id}/convert-to-subtechnique`) + .send({}) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + + it('returns 400 when parentTechniqueAttackId has invalid format', async function () { + await request(app) + .post(`/api/techniques/${technique.stix.id}/convert-to-subtechnique`) + .send({ parentTechniqueAttackId: 'INVALID' }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + + it('returns 404 for non-existent stixId', async function () { + await request(app) + .post('/api/techniques/attack-pattern--does-not-exist/convert-to-subtechnique') + .send({ parentTechniqueAttackId: parentTechnique.workspace.attack_id }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(404); + }); + }); + + // ============================================= + // Convert subtechnique → technique + // ============================================= + describe('POST /api/techniques/:stixId/convert-to-technique', function () { + let parentTechnique; + let subtechnique; + + it('creates a parent technique', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'parent-for-sub', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + parentTechnique = res.body; + }); + + it('creates a subtechnique', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'subtechnique-to-convert', + x_mitre_is_subtechnique: true, + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post(`/api/techniques?parentTechniqueId=${parentTechnique.workspace.attack_id}`) + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + subtechnique = res.body; + expect(subtechnique.stix.x_mitre_is_subtechnique).toBe(true); + expect(subtechnique.workspace.attack_id).toContain('.'); + }); + + it('creates a subtechnique-of relationship', async function () { + const timestamp = new Date().toISOString(); + const relBody = { + workspace: { workflow: { state: 'work-in-progress' } }, + stix: { + spec_version: '2.1', + type: 'relationship', + relationship_type: 'subtechnique-of', + source_ref: subtechnique.stix.id, + target_ref: parentTechnique.stix.id, + created: timestamp, + modified: timestamp, + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + }, + }; + const res = await request(app) + .post('/api/relationships') + .send(relBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + expect(res.body.stix.relationship_type).toBe('subtechnique-of'); + }); + + it('converts the subtechnique to a technique', async function () { + const res = await request(app) + .post(`/api/techniques/${subtechnique.stix.id}/convert-to-technique`) + .send({}) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const converted = res.body; + + // Same STIX ID, new version + expect(converted.stix.id).toBe(subtechnique.stix.id); + expect(converted.stix.modified).not.toBe(subtechnique.stix.modified); + + // Technique fields + expect(converted.stix.x_mitre_is_subtechnique).toBe(false); + expect(converted.workspace.attack_id).toMatch(/^T\d{4}$/); + expect(converted.workspace.attack_id).not.toContain('.'); + + // External reference updated + const attackRef = converted.stix.external_references.find( + (ref) => ref.source_name === 'mitre-attack', + ); + expect(attackRef).toBeDefined(); + expect(attackRef.external_id).toBe(converted.workspace.attack_id); + expect(attackRef.url).toMatch(/\/techniques\/T\d{4}$/); + }); + + it('deprecated the subtechnique-of relationship', async function () { + // The subtechnique-of relationship should now be deprecated + const res = await request(app) + .get( + `/api/relationships?sourceRef=${subtechnique.stix.id}&relationshipType=subtechnique-of&includeDeprecated=true`, + ) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + + // Find the latest version of the relationship + const rels = res.body; + expect(rels.length).toBeGreaterThan(0); + + // At least one version should be deprecated + const deprecatedRel = rels.find((r) => r.stix.x_mitre_deprecated === true); + expect(deprecatedRel).toBeDefined(); + }); + + it('returns 400 when technique is not a subtechnique', async function () { + // The subtechnique was just converted to a technique, so trying again should fail + await request(app) + .post(`/api/techniques/${subtechnique.stix.id}/convert-to-technique`) + .send({}) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + + it('returns 404 for non-existent stixId', async function () { + await request(app) + .post('/api/techniques/attack-pattern--does-not-exist/convert-to-technique') + .send({}) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(404); + }); + }); + + // ============================================= + // Block conversion when technique has child subtechniques + // ============================================= + describe('Block convert-to-subtechnique when technique has children', function () { + let parentTechnique; + let childSubtechnique; + + it('creates a parent technique', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'parent-with-children', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + parentTechnique = res.body; + }); + + it('creates a child subtechnique', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'child-subtechnique', + x_mitre_is_subtechnique: true, + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post(`/api/techniques?parentTechniqueId=${parentTechnique.workspace.attack_id}`) + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + childSubtechnique = res.body; + }); + + it('creates a subtechnique-of relationship from child to parent', async function () { + const timestamp = new Date().toISOString(); + const relBody = { + workspace: { workflow: { state: 'work-in-progress' } }, + stix: { + spec_version: '2.1', + type: 'relationship', + relationship_type: 'subtechnique-of', + source_ref: childSubtechnique.stix.id, + target_ref: parentTechnique.stix.id, + created: timestamp, + modified: timestamp, + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + }, + }; + await request(app) + .post('/api/relationships') + .send(relBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + }); + + it('returns 400 when trying to convert a parent technique that has subtechniques', async function () { + const res = await request(app) + .post(`/api/techniques/${parentTechnique.stix.id}/convert-to-subtechnique`) + .send({ parentTechniqueAttackId: 'T0001' }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + + expect(res.body.details).toContain('subtechnique'); + expect(res.body.details).toContain('Rehome'); + }); + }); + + // ============================================= + // x_mitre_is_subtechnique is preserved on update + // ============================================= + describe('PUT preserves x_mitre_is_subtechnique', function () { + let technique; + + it('creates a technique', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'immutable-subtech-field', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + technique = res.body; + expect(technique.stix.x_mitre_is_subtechnique).toBe(false); + }); + + it('update ignores attempt to change x_mitre_is_subtechnique', async function () { + const updateBody = { + ...technique, + stix: { + ...technique.stix, + x_mitre_is_subtechnique: true, // attempt to change + description: 'Updated description', + }, + }; + + const res = await request(app) + .put(`/api/techniques/${technique.stix.id}/modified/${technique.stix.modified}`) + .send(updateBody) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + + // The field should remain false + expect(res.body.stix.x_mitre_is_subtechnique).toBe(false); + // But the description should have been updated + expect(res.body.stix.description).toBe('Updated description'); + }); + }); + + // ============================================= + // Revoked technique cannot be converted + // ============================================= + describe('Revoked techniques cannot be converted', function () { + let techniqueA; + let techniqueB; + + it('creates two techniques', async function () { + const timestamp1 = new Date().toISOString(); + const body1 = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'revoked-convert-A', + created: timestamp1, + modified: timestamp1, + }, + }; + const res1 = await request(app) + .post('/api/techniques') + .send(body1) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + techniqueA = res1.body; + + const timestamp2 = new Date().toISOString(); + const body2 = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'revoked-convert-B', + created: timestamp2, + modified: timestamp2, + }, + }; + const res2 = await request(app) + .post('/api/techniques') + .send(body2) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + techniqueB = res2.body; + }); + + it('revokes technique A', async function () { + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/revoke`) + .send({ + revoking: { stixId: techniqueB.stix.id, modified: techniqueB.stix.modified }, + }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + }); + + it('returns 400 when trying to convert a revoked technique to subtechnique', async function () { + await request(app) + .post(`/api/techniques/${techniqueA.stix.id}/convert-to-subtechnique`) + .send({ parentTechniqueAttackId: techniqueB.workspace.attack_id }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + }); + + after(async function () { + await database.closeConnection(); + }); +}); From 66e430179ff24e1d64ad201e6e12ab3f008a4234 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:16:05 -0400 Subject: [PATCH 249/370] feat(software): ensure x_mitre_aliases[0] is always the object's own name --- app/services/stix/software-service.js | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/services/stix/software-service.js b/app/services/stix/software-service.js index 5d10441e..896d0d9b 100644 --- a/app/services/stix/software-service.js +++ b/app/services/stix/software-service.js @@ -8,10 +8,36 @@ const softwareRepository = require('../../repository/software-repository'); const { Malware: MalwareType, Tool: ToolType } = require('../../lib/types'); class SoftwareService extends BaseService { + /** + * Ensure x_mitre_aliases[0] is always the object's own name. + * + * - If no aliases exist, sets aliases to [name]. + * - If aliases exist, removes any prior occurrence of the current (and optionally + * previous) name, then prepends the current name at index 0. + * + * @param {Object} data - The software object data (mutated in place) + * @param {string|null} [previousName=null] - The old name to strip on rename + */ + _normalizeAliases(data, previousName = null) { + const name = data.stix?.name; + if (!name) return; + + let aliases = Array.isArray(data.stix.x_mitre_aliases) ? data.stix.x_mitre_aliases : []; + + // Remove current name (avoid duplicate) and previous name (if renamed) + aliases = aliases.filter( + (alias) => alias !== name && (previousName === null || alias !== previousName), + ); + + aliases.unshift(name); + data.stix.x_mitre_aliases = aliases; + } + /** * Set domain-specific defaults before creating a software object. * - For malware: `is_family` defaults to true * - For tools: `is_family` is not allowed + * - Ensures x_mitre_aliases[0] matches the object name * * @param {Object} data - The software object data * @param {Object} _options - Creation options (unused) @@ -26,6 +52,18 @@ class SoftwareService extends BaseService { else if (data.stix && data.stix.type === ToolType && data.stix.is_family !== undefined) { throw new PropertyNotAllowedError('is_family is not allowed for tool objects'); } + + this._normalizeAliases(data); + } + + /** + * Ensure x_mitre_aliases stays in sync on update. + * If the name changed, the old name alias is replaced by the new one. + */ + // eslint-disable-next-line no-unused-vars + async beforeUpdate(_stixId, _stixModified, data, existingDocument, _options) { + const previousName = existingDocument?.stix?.name ?? null; + this._normalizeAliases(data, previousName); } /** From 5a68f64ad3448b8393c080afa22503a970751f1a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:35:09 -0400 Subject: [PATCH 250/370] fix: update revoke workflow to deprecate SROs rather than delete --- app/services/meta-classes/base.service.js | 26 ++++--- app/services/meta-classes/hooks.service.js | 5 +- app/services/stix/relationships-service.js | 73 +++++++++++++++++++ .../api/techniques/techniques.revoke.spec.js | 33 +++++++-- 4 files changed, 117 insertions(+), 20 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 4c613b66..b519d4f3 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -776,9 +776,9 @@ class BaseService extends ServiceWithHooks { * 3. Lifecycle hook: beforeRevoke * 4. Mark Object A as revoked (creates a new version via this.create) * 5. Create a revoked-by relationship (A → B) - * 6. Handle relationships (transfer to B if preserveRelationships, then delete originals) + * 6. Handle relationships (transfer to B if preserveRelationships) * 7. Lifecycle hook: afterRevoke - * 8. Emit revoked event + * 8. Emit revoked event (RelationshipsService deprecates original relationships via event listener) * 9. Return result * * @param {string} stixId - The STIX ID of the object to revoke (Object A) @@ -965,14 +965,6 @@ class BaseService extends ServiceWithHooks { } } - // Delete all original relationships referencing Object A (except the revoked-by) - const excludeIds = [revokedByRelationship.stix.id]; - const deleteResult = await relationshipsRepository.deleteManyBySourceOrTarget( - objectA.stix.id, - excludeIds, - ); - relationshipsSummary.deleted = deleteResult.deletedCount || 0; - // ────────────────────────────────────────────── // 7. LIFECYCLE HOOK: afterRevoke // ────────────────────────────────────────────── @@ -981,7 +973,19 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 8. EMIT EVENT // ────────────────────────────────────────────── - await this.emitRevokedEvent(revokedDocument, objectB, options); + // RelationshipsService listens for revoked events and deprecates all relationships + // referencing the revoked object (except those in excludeRelationshipIds). + // EventBus.emit() awaits all listeners, so deprecation completes before we return. + const excludeRelationshipIds = [revokedByRelationship.stix.id]; + await this.emitRevokedEvent(revokedDocument, objectB, options, { excludeRelationshipIds }); + + // Count deprecated relationships via cross-service READ (allowed by architecture) + const postRevokeRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( + objectA.stix.id, + ); + relationshipsSummary.deprecated = postRevokeRelationships.filter( + (r) => r.stix.x_mitre_deprecated === true && r.stix.id !== revokedByRelationship.stix.id, + ).length; // ────────────────────────────────────────────── // 9. RETURN RESULT diff --git a/app/services/meta-classes/hooks.service.js b/app/services/meta-classes/hooks.service.js index f86b0b68..71ae5073 100644 --- a/app/services/meta-classes/hooks.service.js +++ b/app/services/meta-classes/hooks.service.js @@ -167,8 +167,10 @@ class ServiceWithHooks { * @param {object} revokedDocument - The revoked document * @param {object} revokingDocument - The revoking document * @param {object} options - Revocation options + * @param {object} [metadata] - Additional event metadata + * @param {string[]} [metadata.excludeRelationshipIds] - Relationship STIX IDs to exclude from deprecation */ - async emitRevokedEvent(revokedDocument, revokingDocument, options) { + async emitRevokedEvent(revokedDocument, revokingDocument, options, metadata = {}) { const eventName = `${this.type}::revoked`; logger.info(`Emitting event '${eventName}' for ${revokedDocument.stix.id}`); @@ -179,6 +181,7 @@ class ServiceWithHooks { revokingDocument: revokingDocument.toObject ? revokingDocument.toObject() : revokingDocument, type: this.type, options, + excludeRelationshipIds: metadata.excludeRelationshipIds || [], }); logger.info(`Event '${eventName}' emission complete`); diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index 68b356c6..38100743 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -3,6 +3,9 @@ const { BaseService } = require('../meta-classes'); const relationshipsRepository = require('../../repository/relationships-repository'); const { Relationship: RelationshipType } = require('../../lib/types'); +const EventBus = require('../../lib/event-bus'); +const EventConstants = require('../../lib/event-constants'); +const logger = require('../../lib/logger'); // Map STIX types to ATT&CK types const objectTypeMap = new Map([ @@ -20,6 +23,74 @@ const objectTypeMap = new Map([ ]); class RelationshipsService extends BaseService { + static initializeEventListeners() { + const revokedEvents = [ + EventConstants.ATTACK_PATTERN_REVOKED, + EventConstants.TACTIC_REVOKED, + EventConstants.COURSE_OF_ACTION_REVOKED, + EventConstants.INTRUSION_SET_REVOKED, + EventConstants.MALWARE_REVOKED, + EventConstants.TOOL_REVOKED, + EventConstants.CAMPAIGN_REVOKED, + EventConstants.DATA_SOURCE_REVOKED, + EventConstants.DATA_COMPONENT_REVOKED, + EventConstants.MATRIX_REVOKED, + EventConstants.ASSET_REVOKED, + ]; + + for (const event of revokedEvents) { + EventBus.on(event, this.handleObjectRevoked.bind(this)); + } + + logger.info('RelationshipsService: Event listeners initialized'); + } + + /** + * Handle an object being revoked by deprecating all relationships that reference it. + * Creates a new version of each relationship with x_mitre_deprecated = true and bumped modified, + * preserving the original version in history. + * @param {object} payload - Event payload + * @param {string} payload.stixId - STIX ID of the revoked object + * @param {string[]} [payload.excludeRelationshipIds] - Relationship STIX IDs to skip (e.g. the revoked-by relationship) + */ + static async handleObjectRevoked(payload) { + const { stixId, excludeRelationshipIds = [] } = payload; + + logger.info(`RelationshipsService heard event: object revoked for ${stixId}`); + + const relationships = await relationshipsRepository.retrieveAllBySourceOrTarget(stixId); + + const toDeprecate = relationships.filter( + (rel) => !excludeRelationshipIds.includes(rel.stix.id), + ); + + let deprecatedCount = 0; + for (const rel of toDeprecate) { + try { + const relData = rel.toObject ? rel.toObject() : { ...rel }; + delete relData._id; + delete relData.__v; + delete relData.__t; + + relData.stix.x_mitre_deprecated = true; + relData.stix.modified = new Date().toISOString(); + + await relationshipsRepository.save(relData); + deprecatedCount++; + + logger.info( + `Deprecated relationship ${rel.stix.id} (was referencing revoked object ${stixId})`, + ); + } catch (error) { + logger.error(`Failed to deprecate relationship ${rel.stix.id}: ${error.message}`); + } + } + + logger.info( + `RelationshipsService: deprecated ${deprecatedCount}/${toDeprecate.length} relationships for revoked object ${stixId}`, + ); + } + async retrieveAll(options) { let results = await this.repository.retrieveAll(options); @@ -100,6 +171,8 @@ class RelationshipsService extends BaseService { } } +RelationshipsService.initializeEventListeners(); + // Default export module.exports.RelationshipsService = RelationshipsService; diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js index 9b60f358..a80b78bb 100644 --- a/app/tests/api/techniques/techniques.revoke.spec.js +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -382,14 +382,18 @@ describe('Techniques Revoke API', function () { const result = revokeRes.body; expect(result.relationshipsSummary.transferred).toBe(1); - expect(result.relationshipsSummary.deleted).toBeGreaterThanOrEqual(1); + expect(result.relationshipsSummary.deprecated).toBeGreaterThanOrEqual(1); - // Verify the original relationship was deleted (all versions removed → 404) - await request(app) - .get(`/api/relationships/${originalRel.stix.id}`) + // Verify the original relationship was deprecated (not deleted — history preserved) + const relRes2 = await request(app) + .get(`/api/relationships/${originalRel.stix.id}?versions=latest&includeDeprecated=true`) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) - .expect(404); + .expect(200); + + const deprecatedRels = relRes2.body; + expect(deprecatedRels.length).toBe(1); + expect(deprecatedRels[0].stix.x_mitre_deprecated).toBe(true); // Verify a new relationship was created pointing to technique D const allRelsRes = await request(app) @@ -534,15 +538,28 @@ describe('Techniques Revoke API', function () { expect(mitigatesRels.length).toBe(1); expect(mitigatesRels[0].stix.id).toBe(preExistingRel.stix.id); - // Verify the original M1 → E relationship was deleted + // Verify the original M1 → E relationship was deprecated (not deleted — history preserved) const origRes = await request(app) - .get(`/api/relationships?sourceRef=${mitigationM.stix.id}&targetRef=${techniqueE.stix.id}`) + .get( + `/api/relationships?sourceRef=${mitigationM.stix.id}&targetRef=${techniqueE.stix.id}&includeDeprecated=true`, + ) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); const oldMitigatesRels = origRes.body.filter((r) => r.stix.relationship_type === 'mitigates'); - expect(oldMitigatesRels.length).toBe(0); + expect(oldMitigatesRels.length).toBe(1); + expect(oldMitigatesRels[0].stix.x_mitre_deprecated).toBe(true); + + // Verify it's excluded from default queries (without includeDeprecated) + const defaultRes = await request(app) + .get(`/api/relationships?sourceRef=${mitigationM.stix.id}&targetRef=${techniqueE.stix.id}`) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(200); + + const defaultRels = defaultRes.body.filter((r) => r.stix.relationship_type === 'mitigates'); + expect(defaultRels.length).toBe(0); }); after(async function () { From cb50c9f69582631ba89d535f90fcb986689e3fea Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:26:43 -0400 Subject: [PATCH 251/370] feat(campaigns+groups): ensure aliases[0] is always the object's own name --- app/services/stix/campaigns-service.js | 48 +++++++++++++++++++++++++- app/services/stix/groups-service.js | 48 +++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/app/services/stix/campaigns-service.js b/app/services/stix/campaigns-service.js index 4e504325..3817b12f 100644 --- a/app/services/stix/campaigns-service.js +++ b/app/services/stix/campaigns-service.js @@ -4,6 +4,52 @@ const campaignsRepository = require('../../repository/campaigns-repository'); const { BaseService } = require('../meta-classes'); const { Campaign: CampaignType } = require('../../lib/types'); -class CampaignService extends BaseService {} +class CampaignService extends BaseService { + /** + * Ensure aliases[0] is always the object's own name. + * + * - If no aliases exist, sets aliases to [name]. + * - If aliases exist, removes any prior occurrence of the current (and optionally + * previous) name, then prepends the current name at index 0. + * + * @param {Object} data - The campaign object data (mutated in place) + * @param {string|null} [previousName=null] - The old name to strip on rename + */ + _normalizeAliases(data, previousName = null) { + const name = data.stix?.name; + if (!name) return; + + let aliases = Array.isArray(data.stix.aliases) ? data.stix.aliases : []; + + // Remove current name (avoid duplicate) and previous name (if renamed) + aliases = aliases.filter( + (alias) => alias !== name && (previousName === null || alias !== previousName), + ); + + aliases.unshift(name); + data.stix.aliases = aliases; + } + + /** + * Ensures aliases[0] matches the object name + * + * @param {Object} data - The campaign object data + * @param {Object} _options - Creation options (unused) + */ + // eslint-disable-next-line no-unused-vars + async beforeCreate(data, _options) { + this._normalizeAliases(data); + } + + /** + * Ensure aliases stays in sync on update. + * If the name changed, the old name alias is replaced by the new one. + */ + // eslint-disable-next-line no-unused-vars + async beforeUpdate(_stixId, _stixModified, data, existingDocument, _options) { + const previousName = existingDocument?.stix?.name ?? null; + this._normalizeAliases(data, previousName); + } +} module.exports = new CampaignService(CampaignType, campaignsRepository); diff --git a/app/services/stix/groups-service.js b/app/services/stix/groups-service.js index 51610760..b182470d 100644 --- a/app/services/stix/groups-service.js +++ b/app/services/stix/groups-service.js @@ -4,6 +4,52 @@ const { BaseService } = require('../meta-classes'); const groupsRepository = require('../../repository/groups-repository'); const { Group: GroupType } = require('../../lib/types'); -class GroupsService extends BaseService {} +class GroupsService extends BaseService { + /** + * Ensure aliases[0] is always the object's own name. + * + * - If no aliases exist, sets aliases to [name]. + * - If aliases exist, removes any prior occurrence of the current (and optionally + * previous) name, then prepends the current name at index 0. + * + * @param {Object} data - The group object data (mutated in place) + * @param {string|null} [previousName=null] - The old name to strip on rename + */ + _normalizeAliases(data, previousName = null) { + const name = data.stix?.name; + if (!name) return; + + let aliases = Array.isArray(data.stix.aliases) ? data.stix.aliases : []; + + // Remove current name (avoid duplicate) and previous name (if renamed) + aliases = aliases.filter( + (alias) => alias !== name && (previousName === null || alias !== previousName), + ); + + aliases.unshift(name); + data.stix.aliases = aliases; + } + + /** + * Ensures aliases[0] matches the object name + * + * @param {Object} data - The group object data + * @param {Object} _options - Creation options (unused) + */ + // eslint-disable-next-line no-unused-vars + async beforeCreate(data, _options) { + this._normalizeAliases(data); + } + + /** + * Ensure aliases stays in sync on update. + * If the name changed, the old name alias is replaced by the new one. + */ + // eslint-disable-next-line no-unused-vars + async beforeUpdate(_stixId, _stixModified, data, existingDocument, _options) { + const previousName = existingDocument?.stix?.name ?? null; + this._normalizeAliases(data, previousName); + } +} module.exports = new GroupsService(GroupType, groupsRepository); From 46e3ae4ec3ae6dc29eadcb6253dbbb2dc5ba7cee Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:54:49 -0400 Subject: [PATCH 252/370] test: update unit tests in campaigns.spec and technique.revoke.spec --- app/tests/api/campaigns/campaigns.spec.js | 3 ++- app/tests/api/techniques/techniques.revoke.spec.js | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/tests/api/campaigns/campaigns.spec.js b/app/tests/api/campaigns/campaigns.spec.js index 24ca240c..30f21032 100644 --- a/app/tests/api/campaigns/campaigns.spec.js +++ b/app/tests/api/campaigns/campaigns.spec.js @@ -153,7 +153,8 @@ describe('Campaigns API', function () { expect(campaign1.stix.aliases).toBeDefined(); expect(Array.isArray(campaign1.stix.aliases)).toBe(true); - expect(campaign1.stix.aliases.length).toBe(1); + expect(campaign1.stix.aliases.length).toBe(2); + expect(campaign1.stix.aliases[0]).toBe(campaign1.stix.name); // object_marking_refs should contain the default marking definition expect(campaign1.stix.object_marking_refs).toBeDefined(); diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js index a80b78bb..ad230ba0 100644 --- a/app/tests/api/techniques/techniques.revoke.spec.js +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -39,7 +39,7 @@ describe('Techniques Revoke API', function () { await databaseConfiguration.checkSystemConfiguration(); config.validateRequests.withAttackDataModel = false; - config.validateRequests.withOpenApi = false; + config.validateRequests.withOpenApi = true; app = await require('../../../index').initializeApp(); passportCookie = await login.loginAnonymous(app); @@ -113,7 +113,7 @@ describe('Techniques Revoke API', function () { // techniques collection, resulting in a 404. const timestamp = new Date().toISOString(); const tacticBody = { - workspace: { workflow: {} }, + workspace: { workflow: { state: 'work-in-progress' } }, stix: { name: 'tactic-cross-type', spec_version: '2.1', @@ -350,7 +350,7 @@ describe('Techniques Revoke API', function () { // Create a relationship involving technique C timestamp = new Date().toISOString(); const relBody = { - workspace: { workflow: {} }, + workspace: { workflow: { state: 'work-in-progress' } }, stix: { type: 'relationship', spec_version: '2.1', @@ -386,7 +386,7 @@ describe('Techniques Revoke API', function () { // Verify the original relationship was deprecated (not deleted — history preserved) const relRes2 = await request(app) - .get(`/api/relationships/${originalRel.stix.id}?versions=latest&includeDeprecated=true`) + .get(`/api/relationships/${originalRel.stix.id}?versions=latest`) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200); @@ -470,7 +470,7 @@ describe('Techniques Revoke API', function () { // Create "mitigates" relationship M1 → E timestamp = new Date().toISOString(); const relME = { - workspace: { workflow: {} }, + workspace: { workflow: { state: 'work-in-progress' } }, stix: { type: 'relationship', spec_version: '2.1', @@ -491,7 +491,7 @@ describe('Techniques Revoke API', function () { // Create "mitigates" relationship M1 → F (pre-existing duplicate) timestamp = new Date().toISOString(); const relMF = { - workspace: { workflow: {} }, + workspace: { workflow: { state: 'work-in-progress' } }, stix: { type: 'relationship', spec_version: '2.1', From eec6355e91d5ec8791505ed68d9438049018393b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:35:57 -0400 Subject: [PATCH 253/370] docs: add user documentation explaining the technique conversion workflows --- docs/user/technique-conversion-workflow.md | 217 +++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 docs/user/technique-conversion-workflow.md diff --git a/docs/user/technique-conversion-workflow.md b/docs/user/technique-conversion-workflow.md new file mode 100644 index 00000000..b213ca5a --- /dev/null +++ b/docs/user/technique-conversion-workflow.md @@ -0,0 +1,217 @@ +# Technique Conversion Workflows + +There are two conversion workflows for techniques: + +- **Convert to subtechnique** — promotes a standalone technique to a subtechnique of an existing parent technique. +- **Convert to technique** — promotes a subtechnique to a standalone technique, removing its parent association. + +Both workflows create a new version of the object (same `stix.id`, new `modified` timestamp) with updated properties. The `x_mitre_is_subtechnique` field cannot be changed through a normal PUT update — these endpoints are the only way to change a technique's subtechnique status. + +## Convert Technique to Subtechnique + +### Usage + +``` +POST /api/techniques/:stixId/convert-to-subtechnique +``` + +Where `:stixId` is the STIX ID of the technique to convert (e.g., `attack-pattern--15dbf668-795c-41e6-8219-f0447c0e64ce`). + +Requires **editor** role or higher. + +**Request Body:** + +```json +{ + "parentTechniqueAttackId": "T1234" +} +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `parentTechniqueAttackId` | string | yes | ATT&CK ID of the parent technique. Must match the pattern `T####` and refer to an existing, non-revoked technique. | + +### What Happens + +1. **Validation** — The endpoint verifies that: + - The target technique exists and is not already a subtechnique + - The target technique is not revoked + - The target technique has no child subtechniques (rehome them first) + - The parent technique (`parentTechniqueAttackId`) exists in the system + +2. **New version created** — A new version of the technique is saved with: + - `x_mitre_is_subtechnique` set to `true` + - A new ATT&CK ID in subtechnique format (e.g., `T1234.001`), auto-generated as the next available number under the parent + - Updated `external_references` with the new ATT&CK ID and URL + - A new `modified` timestamp + +3. **Relationship created** — A `subtechnique-of` relationship is created linking the converted subtechnique (`source_ref`) to the parent technique (`target_ref`). + +### Response + +On success, returns **200 OK** with a [workflow response envelope](../../docs/developer/workflow-response-pattern.md): + +```json +{ + "workflow": "convert-to-subtechnique", + "primary": { + "workspace": { + "workflow": { "state": "work-in-progress" }, + "attack_id": "T1234.001" + }, + "stix": { + "type": "attack-pattern", + "id": "attack-pattern--15dbf668-795c-41e6-8219-f0447c0e64ce", + "name": "Example Technique", + "x_mitre_is_subtechnique": true, + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1234.001", + "url": "https://attack.mitre.org/techniques/T1234/001" + } + ] + } + }, + "sideEffects": { + "created": [ + { + "workspace": { "workflow": { "state": "reviewed" } }, + "stix": { + "type": "relationship", + "relationship_type": "subtechnique-of", + "source_ref": "attack-pattern--15dbf668-795c-41e6-8219-f0447c0e64ce", + "target_ref": "attack-pattern--a1234567-abcd-1234-abcd-1234567890ab" + } + } + ], + "modified": [], + "deprecated": [], + "deleted": { "count": 0, "stixIds": [] } + }, + "warnings": [] +} +``` + +| Field | Description | +|-------|-------------| +| `workflow` | Always `"convert-to-subtechnique"` | +| `primary` | The technique in its post-conversion state (new version) | +| `sideEffects.created` | The `subtechnique-of` relationship linking the new subtechnique to its parent | +| `sideEffects.deprecated` | Empty for this workflow | +| `warnings` | Non-fatal issues encountered during the workflow | + +### Error Responses + +#### 400 Bad Request + +| Condition | Details | +|-----------|---------| +| Technique is already a subtechnique | `Technique attack-pattern--... is already a subtechnique` | +| Technique is revoked | `Cannot convert a revoked technique` | +| Technique has child subtechniques | `Technique attack-pattern--... has N subtechnique(s). Rehome or remove the subtechnique-of relationships before converting this technique to a subtechnique.` | +| Parent technique does not exist | `Parent technique with ATT&CK ID T#### not found` | +| `parentTechniqueAttackId` missing | Missing parameter error | +| `parentTechniqueAttackId` invalid format | `Invalid parent technique ATT&CK ID format: .... Must be T####.` | + +#### 404 Not Found + +Returned when the target technique (`stixId`) does not exist. + +--- + +## Convert Subtechnique to Technique + +### Usage + +``` +POST /api/techniques/:stixId/convert-to-technique +``` + +Where `:stixId` is the STIX ID of the subtechnique to convert. + +Requires **editor** role or higher. + +No request body is required. + +### What Happens + +1. **Validation** — The endpoint verifies that: + - The target technique exists and is currently a subtechnique (`x_mitre_is_subtechnique` is `true`) + - The target technique is not revoked + +2. **New version created** — A new version of the technique is saved with: + - `x_mitre_is_subtechnique` set to `false` + - A new ATT&CK ID in technique format (e.g., `T1235`), auto-generated as the next available number + - Updated `external_references` with the new ATT&CK ID and URL + - A new `modified` timestamp + +3. **Relationship deprecated** — Any active `subtechnique-of` relationships where this object is the `source_ref` are deprecated (a new version of each relationship is created with `x_mitre_deprecated` set to `true`). The original relationship versions are preserved in history. + +### Response + +On success, returns **200 OK** with a [workflow response envelope](../../docs/developer/workflow-response-pattern.md): + +```json +{ + "workflow": "convert-to-technique", + "primary": { + "workspace": { + "workflow": { "state": "work-in-progress" }, + "attack_id": "T1235" + }, + "stix": { + "type": "attack-pattern", + "id": "attack-pattern--15dbf668-795c-41e6-8219-f0447c0e64ce", + "name": "Example Subtechnique", + "x_mitre_is_subtechnique": false, + "external_references": [ + { + "source_name": "mitre-attack", + "external_id": "T1235", + "url": "https://attack.mitre.org/techniques/T1235" + } + ] + } + }, + "sideEffects": { + "created": [], + "modified": [], + "deprecated": [ + { + "workspace": { "workflow": { "state": "reviewed" } }, + "stix": { + "type": "relationship", + "relationship_type": "subtechnique-of", + "source_ref": "attack-pattern--15dbf668-795c-41e6-8219-f0447c0e64ce", + "target_ref": "attack-pattern--a1234567-abcd-1234-abcd-1234567890ab", + "x_mitre_deprecated": true + } + } + ], + "deleted": { "count": 0, "stixIds": [] } + }, + "warnings": [] +} +``` + +| Field | Description | +|-------|-------------| +| `workflow` | Always `"convert-to-technique"` | +| `primary` | The technique in its post-conversion state (new version) | +| `sideEffects.deprecated` | The `subtechnique-of` relationship(s) that were deprecated | +| `sideEffects.created` | Empty for this workflow | +| `warnings` | Non-fatal issues encountered during the workflow | + +### Error Responses + +#### 400 Bad Request + +| Condition | Details | +|-----------|---------| +| Technique is not a subtechnique | `Technique attack-pattern--... is not a subtechnique` | +| Technique is revoked | `Cannot convert a revoked technique` | + +#### 404 Not Found + +Returned when the target technique (`stixId`) does not exist. From 9b62521a95a9423d3e68a15d21e6637cd258d8d6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:38:50 -0400 Subject: [PATCH 254/370] feat: allow handlers to return content asynchronously over event bus Currently emit() awaits handlers via Promise.allSettled but discards return values. Modified it to collect and return fulfilled values. Non-breaking: no existing caller inspects the return value. --- app/lib/event-bus.js | 6 +++++- app/services/meta-classes/hooks.service.js | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/lib/event-bus.js b/app/lib/event-bus.js index 8272ba53..fa00dd3c 100644 --- a/app/lib/event-bus.js +++ b/app/lib/event-bus.js @@ -62,8 +62,9 @@ class EventBus extends EventEmitter { try { const listenerName = listener.name || 'anonymous'; logger.debug(`EventBus: Executing listener '${listenerName}' for '${eventName}'`); - await listener(payload); + const result = await listener(payload); logger.debug(`EventBus: Listener '${listenerName}' completed successfully`); + return result; } catch (error) { const listenerName = listener.name || 'anonymous'; logger.error(`EventBus: Listener '${listenerName}' failed for '${eventName}':`, error); @@ -79,6 +80,9 @@ class EventBus extends EventEmitter { `EventBus: ${failures.length}/${listeners.length} listeners failed for '${eventName}'`, ); } + + // Return fulfilled handler results for callers that need them (e.g., WorkflowResult) + return results.filter((r) => r.status === 'fulfilled' && r.value != null).map((r) => r.value); } /** diff --git a/app/services/meta-classes/hooks.service.js b/app/services/meta-classes/hooks.service.js index 71ae5073..d3999433 100644 --- a/app/services/meta-classes/hooks.service.js +++ b/app/services/meta-classes/hooks.service.js @@ -175,7 +175,7 @@ class ServiceWithHooks { logger.info(`Emitting event '${eventName}' for ${revokedDocument.stix.id}`); - await EventBus.emit(eventName, { + const results = await EventBus.emit(eventName, { stixId: revokedDocument.stix.id, revokedDocument: revokedDocument.toObject ? revokedDocument.toObject() : revokedDocument, revokingDocument: revokingDocument.toObject ? revokingDocument.toObject() : revokingDocument, @@ -185,6 +185,7 @@ class ServiceWithHooks { }); logger.info(`Event '${eventName}' emission complete`); + return results; } } From 9e3be59a937d65c925a79143b8bb50ddf1022f01 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:40:51 -0400 Subject: [PATCH 255/370] feat: add DTO pattern to handle workflow results Will be used by the revoked workflow, as well as the two technique conversion workflows. This lays the groundwork for a universal workflow response structure. --- .../components/workflow-response.yml | 95 ++++++++++ app/lib/workflow-result.js | 162 ++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 app/api/definitions/components/workflow-response.yml create mode 100644 app/lib/workflow-result.js diff --git a/app/api/definitions/components/workflow-response.yml b/app/api/definitions/components/workflow-response.yml new file mode 100644 index 00000000..61e08903 --- /dev/null +++ b/app/api/definitions/components/workflow-response.yml @@ -0,0 +1,95 @@ +components: + schemas: + workflow-response: + type: object + description: | + Universal response envelope for all workflow endpoints (revoke, convert-to-subtechnique, + convert-to-technique). Provides full visibility into every object that was created, + modified, deprecated, or deleted as a consequence of the request. + required: + - workflow + - primary + - sideEffects + - warnings + properties: + workflow: + type: string + description: 'Discriminator identifying which workflow produced this response.' + enum: + - revoke + - convert-to-subtechnique + - convert-to-technique + example: 'revoke' + primary: + type: object + description: 'The primary object that the user acted on, in its post-workflow state.' + properties: + workspace: + $ref: 'workspace.yml#/components/schemas/workspace' + stix: + $ref: 'stix-common.yml#/components/schemas/stix-common' + sideEffects: + $ref: '#/components/schemas/side-effects' + warnings: + type: array + description: 'Non-fatal warning messages generated during the workflow.' + items: + type: string + example: [] + + side-effects: + type: object + description: 'Objects created, modified, deprecated, or deleted as a consequence of the workflow.' + required: + - created + - modified + - deprecated + - deleted + properties: + created: + type: array + description: 'Objects created during the workflow (e.g., revoked-by relationships, subtechnique-of relationships, transferred relationships).' + items: + type: object + properties: + workspace: + $ref: 'workspace.yml#/components/schemas/workspace' + stix: + $ref: 'stix-common.yml#/components/schemas/stix-common' + modified: + type: array + description: 'Objects modified (new version saved) during the workflow.' + items: + type: object + properties: + workspace: + $ref: 'workspace.yml#/components/schemas/workspace' + stix: + $ref: 'stix-common.yml#/components/schemas/stix-common' + deprecated: + type: array + description: 'Objects deprecated (new version saved with x_mitre_deprecated=true) during the workflow.' + items: + type: object + properties: + workspace: + $ref: 'workspace.yml#/components/schemas/workspace' + stix: + $ref: 'stix-common.yml#/components/schemas/stix-common' + deleted: + type: object + description: 'Summary of hard-deleted objects (if any).' + required: + - count + - stixIds + properties: + count: + type: integer + description: 'Number of hard-deleted objects.' + example: 0 + stixIds: + type: array + description: 'STIX IDs of hard-deleted objects.' + items: + type: string + example: [] diff --git a/app/lib/workflow-result.js b/app/lib/workflow-result.js new file mode 100644 index 00000000..a9fe2f0b --- /dev/null +++ b/app/lib/workflow-result.js @@ -0,0 +1,162 @@ +'use strict'; + +/** + * DTO builder for universal workflow endpoint responses. + * + * All workflow endpoints (revoke, convert-to-subtechnique, convert-to-technique) + * return a WorkflowResult so that the caller has full visibility into every + * object that was created, modified, deprecated, or deleted as a consequence + * of their request. + * + * @see docs/developer/workflow-response-pattern.md + */ +class WorkflowResult { + /** + * @param {string} workflowName - Discriminator string (e.g., 'revoke', 'convert-to-subtechnique') + */ + constructor(workflowName) { + this.workflow = workflowName; + this.primary = null; + this.sideEffects = { + created: [], + modified: [], + deprecated: [], + deleted: { count: 0, stixIds: [] }, + }; + this.warnings = []; + } + + /** + * Set the primary object that the user acted on. + * @param {Object} document - Full workspace+stix document (Mongoose doc or plain object) + */ + setPrimary(document) { + this.primary = document; + } + + /** + * Add one or more documents to the created side-effects list. + * @param {Object|Array} docOrDocs - Document(s) created as a consequence + */ + addCreated(docOrDocs) { + this._pushDocs(this.sideEffects.created, docOrDocs); + } + + /** + * Add one or more documents to the modified side-effects list. + * @param {Object|Array} docOrDocs - Document(s) modified as a consequence + */ + addModified(docOrDocs) { + this._pushDocs(this.sideEffects.modified, docOrDocs); + } + + /** + * Add one or more documents to the deprecated side-effects list. + * @param {Object|Array} docOrDocs - Document(s) deprecated as a consequence + */ + addDeprecated(docOrDocs) { + this._pushDocs(this.sideEffects.deprecated, docOrDocs); + } + + /** + * Record hard-deleted documents by their STIX IDs. + * @param {Array} stixIds - STIX IDs of deleted documents + */ + addDeleted(stixIds) { + if (!Array.isArray(stixIds)) return; + this.sideEffects.deleted.stixIds.push(...stixIds); + this.sideEffects.deleted.count = this.sideEffects.deleted.stixIds.length; + } + + /** + * Add a single warning message. + * @param {string} message + */ + addWarning(message) { + this.warnings.push(message); + } + + /** + * Add multiple warning messages. + * @param {Array} messages + */ + addWarnings(messages) { + if (!Array.isArray(messages)) return; + this.warnings.push(...messages); + } + + /** + * Merge results returned by EventBus.emit() into this WorkflowResult. + * + * Each element in eventResults is an object returned by an event handler + * with any subset of: { created, modified, deprecated, warnings }. + * + * @param {Array} eventResults - Array of handler return values + */ + mergeEventResults(eventResults) { + if (!Array.isArray(eventResults)) return; + for (const handlerResult of eventResults) { + if (!handlerResult || typeof handlerResult !== 'object') continue; + if (handlerResult.created) this.addCreated(handlerResult.created); + if (handlerResult.modified) this.addModified(handlerResult.modified); + if (handlerResult.deprecated) this.addDeprecated(handlerResult.deprecated); + if (handlerResult.warnings) this.addWarnings(handlerResult.warnings); + } + } + + /** + * Serialize to a plain JSON-safe object. + * + * Calls .toObject() on any Mongoose documents and strips internal fields + * (_id, __v, __t) from all documents. + * + * @returns {Object} Plain object suitable for res.json() + */ + toJSON() { + return { + workflow: this.workflow, + primary: WorkflowResult._toPlain(this.primary), + sideEffects: { + created: this.sideEffects.created.map(WorkflowResult._toPlain), + modified: this.sideEffects.modified.map(WorkflowResult._toPlain), + deprecated: this.sideEffects.deprecated.map(WorkflowResult._toPlain), + deleted: { ...this.sideEffects.deleted }, + }, + warnings: [...this.warnings], + }; + } + + // ── Private helpers ────────────────────────────────────────────────── + + /** + * Push one or more documents into an array. + * @param {Array} target + * @param {Object|Array} docOrDocs + * @private + */ + _pushDocs(target, docOrDocs) { + if (Array.isArray(docOrDocs)) { + target.push(...docOrDocs); + } else if (docOrDocs) { + target.push(docOrDocs); + } + } + + /** + * Convert a Mongoose document (or plain object) to a clean plain object. + * Strips _id, __v, __t which are internal Mongoose/MongoDB fields. + * @param {Object} doc + * @returns {Object} + * @private + */ + static _toPlain(doc) { + if (!doc) return doc; + const plain = typeof doc.toObject === 'function' ? doc.toObject() : { ...doc }; + delete plain._id; + delete plain.__v; + delete plain.__t; + return plain; + } +} + +module.exports = WorkflowResult; From ddd73c60a3379d8fc53da575dd4ab103e5547096 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:46:11 -0400 Subject: [PATCH 256/370] feat: update technique conversion workflows to use universal workflow response schema Also adds a new universal repository method, retrieveLatestByAttackId, for getting the latest version of an object by its attack id --- .../definitions/paths/techniques-paths.yml | 14 +++- app/controllers/techniques-controller.js | 4 +- app/repository/_base.repository.js | 17 ++++ app/services/stix/techniques-service.js | 42 ++++++---- .../api/techniques/techniques.convert.spec.js | 82 ++++++++++++++----- 5 files changed, 118 insertions(+), 41 deletions(-) diff --git a/app/api/definitions/paths/techniques-paths.yml b/app/api/definitions/paths/techniques-paths.yml index 68caec30..918372ee 100644 --- a/app/api/definitions/paths/techniques-paths.yml +++ b/app/api/definitions/paths/techniques-paths.yml @@ -409,9 +409,9 @@ paths: content: application/json: schema: - $ref: '../components/techniques.yml#/components/schemas/technique' + $ref: '../components/workflow-response.yml#/components/schemas/workflow-response' '400': - description: 'Invalid request. The technique is already a subtechnique, is revoked, or the parent ID is invalid.' + description: 'Invalid request. The technique is already a subtechnique, is revoked, or the parent technique does not exist.' '404': description: 'The technique was not found.' @@ -425,6 +425,7 @@ paths: - Generates a new technique-format ATT&CK ID (e.g., T1235) - Sets `x_mitre_is_subtechnique` to `false` - Rebuilds the ATT&CK external reference with the new ID and URL + - Deprecates any existing subtechnique-of relationships - Persists the result as a new version of the object The technique must currently be a subtechnique and must not be revoked. @@ -443,7 +444,7 @@ paths: content: application/json: schema: - $ref: '../components/techniques.yml#/components/schemas/technique' + $ref: '../components/workflow-response.yml#/components/schemas/workflow-response' '400': description: 'Invalid request. The object is not a subtechnique or is revoked.' '404': @@ -456,6 +457,7 @@ paths: description: | Revokes the technique identified by the stixId path parameter in favor of the revoking object specified in the request body. Optionally transfers relationships from the revoked object to the revoking object. + Deprecates all relationships referencing the revoked object. tags: - 'Techniques' parameters: @@ -467,7 +469,7 @@ paths: type: string - name: preserveRelationships in: query - description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deletion.' + description: 'If true, relationships referencing the revoked object are cloned to point to the revoking object before deprecation.' schema: type: boolean default: false @@ -495,6 +497,10 @@ paths: responses: '200': description: 'The technique was successfully revoked.' + content: + application/json: + schema: + $ref: '../components/workflow-response.yml#/components/schemas/workflow-response' '400': description: 'Missing or invalid parameters.' '404': diff --git a/app/controllers/techniques-controller.js b/app/controllers/techniques-controller.js index af190521..68394bde 100644 --- a/app/controllers/techniques-controller.js +++ b/app/controllers/techniques-controller.js @@ -200,7 +200,7 @@ exports.convertToSubtechnique = async function (req, res, next) { req.body, options, ); - logger.debug('Success: Converted technique to subtechnique ' + result.stix.id); + logger.debug('Success: Converted technique to subtechnique ' + result.primary?.stix?.id); return res.status(200).send(result); } catch (err) { return next(err); @@ -213,7 +213,7 @@ exports.convertToTechnique = async function (req, res, next) { userAccountId: req.user?.userAccountId, }; const result = await techniquesService.convertToTechnique(req.params.stixId, options); - logger.debug('Success: Converted subtechnique to technique ' + result.stix.id); + logger.debug('Success: Converted subtechnique to technique ' + result.primary?.stix?.id); return res.status(200).send(result); } catch (err) { return next(err); diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index ba6664ef..b407550c 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -202,6 +202,23 @@ class BaseRepository extends AbstractRepository { } } + /** + * Retrieve the latest version of an object by its ATT&CK ID (e.g., "T1234", "G0001"). + * + * @param {string} attackId - The workspace ATT&CK ID to look up + * @returns {Promise} The latest object version, or null if not found + */ + async retrieveLatestByAttackId(attackId) { + try { + return await this.model + .findOne({ 'workspace.attack_id': attackId }) + .sort('-stix.modified') + .exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + async retrieveLatestByStixIdLean(stixId) { try { return await this.model diff --git a/app/services/stix/techniques-service.js b/app/services/stix/techniques-service.js index 82642e0d..fbff7af9 100644 --- a/app/services/stix/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -18,6 +18,7 @@ const { } = require('../../lib/external-reference-builder'); const EventBus = require('../../lib/event-bus'); const EventConstants = require('../../lib/event-constants'); +const WorkflowResult = require('../../lib/workflow-result'); const logger = require('../../lib/logger'); /** @@ -217,6 +218,16 @@ class TechniquesService extends BaseService { }); } + // Validate that the parent technique exists + const parentTechnique = await this.repository.retrieveLatestByAttackId( + data.parentTechniqueAttackId, + ); + if (!parentTechnique) { + throw new BadRequestError({ + details: `Parent technique with ATT&CK ID ${data.parentTechniqueAttackId} not found`, + }); + } + // Check if this technique has child subtechniques (via subtechnique-of SROs). // Cross-service READ is permitted per architecture guidelines. // If children exist, block the conversion — the user must rehome them first. @@ -272,16 +283,18 @@ class TechniquesService extends BaseService { `Converted technique ${stixId} to subtechnique: ${technique.workspace?.attack_id} -> ${newAttackId}`, ); - // Emit domain event for cross-service coordination - await EventBus.emit(EventConstants.TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE, { - stixId, - document: savedDocument.toObject ? savedDocument.toObject() : savedDocument, - previousAttackId: technique.workspace?.attack_id, - newAttackId, - parentTechniqueAttackId: data.parentTechniqueAttackId, + const result = new WorkflowResult('convert-to-subtechnique'); + result.setPrimary(savedDocument); + + // Emit domain event — RelationshipsService listens to create the subtechnique-of SRO + const eventResults = await EventBus.emit(EventConstants.TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE, { + stixId /** STIX ID of the converted subtechnique */, + parentStixId: parentTechnique.stix.id /** STIX ID of the parent technique */, + userAccountId: options.userAccountId, }); + result.mergeEventResults(eventResults); - return savedDocument.toObject ? savedDocument.toObject() : savedDocument; + return result.toJSON(); } /** @@ -352,15 +365,16 @@ class TechniquesService extends BaseService { `Converted subtechnique ${stixId} to technique: ${technique.workspace?.attack_id} -> ${newAttackId}`, ); + const result = new WorkflowResult('convert-to-technique'); + result.setPrimary(savedDocument); + // Emit domain event — RelationshipsService listens to deprecate subtechnique-of SROs - await EventBus.emit(EventConstants.SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE, { - stixId, - document: savedDocument.toObject ? savedDocument.toObject() : savedDocument, - previousAttackId: technique.workspace?.attack_id, - newAttackId, + const eventResults = await EventBus.emit(EventConstants.SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE, { + stixId /** STIX ID of the converted subtechnique */, }); + result.mergeEventResults(eventResults); - return savedDocument.toObject ? savedDocument.toObject() : savedDocument; + return result.toJSON(); } async retrieveTacticsForTechnique(stixId, modified, options) { diff --git a/app/tests/api/techniques/techniques.convert.spec.js b/app/tests/api/techniques/techniques.convert.spec.js index 9b31a19e..8798ed42 100644 --- a/app/tests/api/techniques/techniques.convert.spec.js +++ b/app/tests/api/techniques/techniques.convert.spec.js @@ -103,7 +103,14 @@ describe('Techniques Convert API', function () { .expect(200) .expect('Content-Type', /json/); - const converted = res.body; + const body = res.body; + + // WorkflowResult envelope + expect(body.workflow).toBe('convert-to-subtechnique'); + expect(body.primary).toBeDefined(); + expect(body.sideEffects).toBeDefined(); + + const converted = body.primary; // Same STIX ID, new version expect(converted.stix.id).toBe(technique.stix.id); @@ -122,6 +129,13 @@ describe('Techniques Convert API', function () { expect(attackRef).toBeDefined(); expect(attackRef.external_id).toBe(converted.workspace.attack_id); expect(attackRef.url).toContain('/techniques/'); + + // Side effect: subtechnique-of relationship was created + expect(body.sideEffects.created.length).toBe(1); + const createdRel = body.sideEffects.created[0]; + expect(createdRel.stix.relationship_type).toBe('subtechnique-of'); + expect(createdRel.stix.source_ref).toBe(technique.stix.id); + expect(createdRel.stix.target_ref).toBe(parentTechnique.stix.id); }); it('returns 400 when technique is already a subtechnique', async function () { @@ -151,6 +165,16 @@ describe('Techniques Convert API', function () { .expect(400); }); + it('returns 400 when parentTechniqueAttackId does not exist', async function () { + // T9999 has valid format but no technique with this ATT&CK ID exists + await request(app) + .post(`/api/techniques/${technique.stix.id}/convert-to-subtechnique`) + .send({ parentTechniqueAttackId: 'T9999' }) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(400); + }); + it('returns 404 for non-existent stixId', async function () { await request(app) .post('/api/techniques/attack-pattern--does-not-exist/convert-to-subtechnique') @@ -248,7 +272,14 @@ describe('Techniques Convert API', function () { .expect(200) .expect('Content-Type', /json/); - const converted = res.body; + const body = res.body; + + // WorkflowResult envelope + expect(body.workflow).toBe('convert-to-technique'); + expect(body.primary).toBeDefined(); + expect(body.sideEffects).toBeDefined(); + + const converted = body.primary; // Same STIX ID, new version expect(converted.stix.id).toBe(subtechnique.stix.id); @@ -266,25 +297,12 @@ describe('Techniques Convert API', function () { expect(attackRef).toBeDefined(); expect(attackRef.external_id).toBe(converted.workspace.attack_id); expect(attackRef.url).toMatch(/\/techniques\/T\d{4}$/); - }); - - it('deprecated the subtechnique-of relationship', async function () { - // The subtechnique-of relationship should now be deprecated - const res = await request(app) - .get( - `/api/relationships?sourceRef=${subtechnique.stix.id}&relationshipType=subtechnique-of&includeDeprecated=true`, - ) - .set('Accept', 'application/json') - .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) - .expect(200); - - // Find the latest version of the relationship - const rels = res.body; - expect(rels.length).toBeGreaterThan(0); - // At least one version should be deprecated - const deprecatedRel = rels.find((r) => r.stix.x_mitre_deprecated === true); - expect(deprecatedRel).toBeDefined(); + // Side effect: subtechnique-of relationship was deprecated + expect(body.sideEffects.deprecated.length).toBeGreaterThan(0); + const deprecatedRel = body.sideEffects.deprecated[0]; + expect(deprecatedRel.stix.relationship_type).toBe('subtechnique-of'); + expect(deprecatedRel.stix.x_mitre_deprecated).toBe(true); }); it('returns 400 when technique is not a subtechnique', async function () { @@ -313,6 +331,7 @@ describe('Techniques Convert API', function () { describe('Block convert-to-subtechnique when technique has children', function () { let parentTechnique; let childSubtechnique; + let wouldBeParent; it('creates a parent technique', async function () { const timestamp = new Date().toISOString(); @@ -335,6 +354,27 @@ describe('Techniques Convert API', function () { parentTechnique = res.body; }); + it('creates a would-be parent technique for the conversion attempt', async function () { + const timestamp = new Date().toISOString(); + const body = { + ...baseTechniqueData, + stix: { + ...baseTechniqueData.stix, + name: 'would-be-parent', + created: timestamp, + modified: timestamp, + }, + }; + const res = await request(app) + .post('/api/techniques') + .send(body) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201); + + wouldBeParent = res.body; + }); + it('creates a child subtechnique', async function () { const timestamp = new Date().toISOString(); const body = { @@ -384,7 +424,7 @@ describe('Techniques Convert API', function () { it('returns 400 when trying to convert a parent technique that has subtechniques', async function () { const res = await request(app) .post(`/api/techniques/${parentTechnique.stix.id}/convert-to-subtechnique`) - .send({ parentTechniqueAttackId: 'T0001' }) + .send({ parentTechniqueAttackId: wouldBeParent.workspace.attack_id }) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(400); From c9c43a187dbda37188370ec3c635f85f22eb1412 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:48:05 -0400 Subject: [PATCH 257/370] feat: implement relationship handlers for technique to subtechnique workflow Also refactors both technique conversion workflow handlers to use new universal workflow result response DTO pattern --- app/services/stix/relationships-service.js | 137 ++++++++++++++++----- 1 file changed, 106 insertions(+), 31 deletions(-) diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index 8d09c31c..bfc743f0 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -46,14 +46,73 @@ class RelationshipsService extends BaseService { EventBus.on(event, this.handleObjectRevoked.bind(this)); } + EventBus.on( + EventConstants.TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE, + this.handleTechniqueConvertedToSubtechnique.bind(this), + ); + EventBus.on( EventConstants.SUBTECHNIQUE_CONVERTED_TO_TECHNIQUE, - RelationshipsService.handleSubtechniqueConvertedToTechnique.bind(RelationshipsService), + this.handleSubtechniqueConvertedToTechnique.bind(this), ); logger.info('RelationshipsService: Event listeners initialized'); } + /** + * Create a subtechnique-of SRO when a technique is converted to a subtechnique. + * + * Uses the service instance's create() method (via module.exports singleton) + * so that the relationship gets a proper stix.id, server-controlled fields, + * and ADM validation — the same path as any user-created relationship. + * + * @param {Object} payload - Event payload + * @param {string} payload.stixId - STIX ID of the converted subtechnique + * @param {string} payload.parentStixId - STIX ID of the parent technique + * @param {string} [payload.userAccountId] - Authenticated user's account ID + */ + static async handleTechniqueConvertedToSubtechnique(payload) { + const { stixId, parentStixId, userAccountId } = payload; + + logger.info( + `RelationshipsService: Creating subtechnique-of relationship for ${stixId} -> ${parentStixId}`, + ); + + try { + // Use the singleton instance exported by this module + const relationshipsService = module.exports; + const now = new Date().toISOString(); + const createdRelationship = await relationshipsService.create( + { + workspace: { + workflow: { state: 'reviewed' }, // TODO introduce a new workflow state for entities that are never reviewed by users; for now, set to 'reviewed' to ensure they undergo full ADM validation + }, + stix: { + type: 'relationship', + spec_version: '2.1', + relationship_type: 'subtechnique-of', + source_ref: stixId, + target_ref: parentStixId, + created: now, + modified: now, + }, + }, + { userAccountId }, + ); + + logger.info( + `RelationshipsService: Created subtechnique-of relationship for ${stixId} -> ${parentStixId}`, + ); + + return { created: [createdRelationship] }; + } catch (error) { + logger.error( + `RelationshipsService: Error creating subtechnique-of relationship for ${stixId}: ${error.message}`, + ); + return { warnings: [`Failed to create subtechnique-of relationship for ${stixId}`] }; + } + } + /** * Deprecate subtechnique-of SROs when a subtechnique is converted to a technique. * @@ -68,6 +127,9 @@ class RelationshipsService extends BaseService { logger.info(`RelationshipsService: Deprecating subtechnique-of relationships for ${stixId}`); + const deprecatedDocs = []; + const warnings = []; + try { const subtechniqueOfRels = await relationshipsRepository.retrieveAll({ sourceRef: stixId, @@ -77,7 +139,6 @@ class RelationshipsService extends BaseService { includeDeprecated: false, }); - let deprecatedCount = 0; for (const rel of subtechniqueOfRels) { try { const deprecatedVersion = rel.toObject ? rel.toObject() : { ...rel }; @@ -88,24 +149,28 @@ class RelationshipsService extends BaseService { deprecatedVersion.stix.x_mitre_deprecated = true; deprecatedVersion.stix.modified = new Date().toISOString(); - await relationshipsRepository.save(deprecatedVersion); - deprecatedCount++; + const saved = await relationshipsRepository.save(deprecatedVersion); + deprecatedDocs.push(saved); } catch (error) { logger.error( `RelationshipsService: Error deprecating relationship ${rel.stix?.id}: ${error.message}`, ); + warnings.push(`Failed to deprecate relationship ${rel.stix?.id}`); } } logger.info( - `RelationshipsService: Deprecated ${deprecatedCount}/${subtechniqueOfRels.length} subtechnique-of relationship(s) for ${stixId}`, + `RelationshipsService: Deprecated ${deprecatedDocs.length}/${subtechniqueOfRels.length} subtechnique-of relationship(s) for ${stixId}`, ); } catch (error) { logger.error( `RelationshipsService: Error handling subtechnique-to-technique conversion for ${stixId}:`, error, ); + warnings.push(`Failed to deprecate subtechnique-of relationships for ${stixId}`); } + + return { deprecated: deprecatedDocs, warnings }; } /** @@ -121,37 +186,47 @@ class RelationshipsService extends BaseService { logger.info(`RelationshipsService heard event: object revoked for ${stixId}`); - const relationships = await relationshipsRepository.retrieveAllBySourceOrTarget(stixId); + const deprecatedDocs = []; + const warnings = []; - const toDeprecate = relationships.filter( - (rel) => !excludeRelationshipIds.includes(rel.stix.id), - ); + try { + const relationships = await relationshipsRepository.retrieveAllBySourceOrTarget(stixId); + + const toDeprecate = relationships.filter( + (rel) => !excludeRelationshipIds.includes(rel.stix.id), + ); + + for (const rel of toDeprecate) { + try { + const relData = rel.toObject ? rel.toObject() : { ...rel }; + delete relData._id; + delete relData.__v; + delete relData.__t; - let deprecatedCount = 0; - for (const rel of toDeprecate) { - try { - const relData = rel.toObject ? rel.toObject() : { ...rel }; - delete relData._id; - delete relData.__v; - delete relData.__t; - - relData.stix.x_mitre_deprecated = true; - relData.stix.modified = new Date().toISOString(); - - await relationshipsRepository.save(relData); - deprecatedCount++; - - logger.info( - `Deprecated relationship ${rel.stix.id} (was referencing revoked object ${stixId})`, - ); - } catch (error) { - logger.error(`Failed to deprecate relationship ${rel.stix.id}: ${error.message}`); + relData.stix.x_mitre_deprecated = true; + relData.stix.modified = new Date().toISOString(); + + const saved = await relationshipsRepository.save(relData); + deprecatedDocs.push(saved); + + logger.info( + `Deprecated relationship ${rel.stix.id} (was referencing revoked object ${stixId})`, + ); + } catch (error) { + logger.error(`Failed to deprecate relationship ${rel.stix.id}: ${error.message}`); + warnings.push(`Failed to deprecate relationship ${rel.stix.id}`); + } } + + logger.info( + `RelationshipsService: deprecated ${deprecatedDocs.length}/${toDeprecate.length} relationships for revoked object ${stixId}`, + ); + } catch (error) { + logger.error(`RelationshipsService: Error handling object revoked for ${stixId}:`, error); + warnings.push(`Failed to deprecate relationships for revoked object ${stixId}`); } - logger.info( - `RelationshipsService: deprecated ${deprecatedCount}/${toDeprecate.length} relationships for revoked object ${stixId}`, - ); + return { deprecated: deprecatedDocs, warnings }; } async retrieveAll(options) { From ee8d9607aa27f5f2afbf97256754f74033263c5c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:49:21 -0400 Subject: [PATCH 258/370] refactor: update revoke workflow to use universal workflow response schema --- app/services/meta-classes/base.service.js | 44 +++---- .../api/techniques/techniques.revoke.spec.js | 38 +++--- docs/user/revoke-workflow.md | 111 +++++++----------- 3 files changed, 80 insertions(+), 113 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 928e92e1..21c0f3ca 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -24,6 +24,7 @@ const { } = require('../../exceptions'); const { getSchema, processValidationIssues } = require('../system/validate-service'); const ServiceWithHooks = require('./hooks.service'); +const WorkflowResult = require('../../lib/workflow-result'); // Import required repositories const systemConfigurationRepository = require('../../repository/system-configurations-repository'); @@ -870,9 +871,15 @@ class BaseService extends ServiceWithHooks { const revokedDocument = await this.repository.save(objectAData); + const result = new WorkflowResult('revoke'); + result.setPrimary(revokedDocument); + // ────────────────────────────────────────────── // 5. CREATE REVOKED-BY RELATIONSHIP // ────────────────────────────────────────────── + // NOTE: This is a direct cross-service write (BaseService → RelationshipsService.create). + // The revoke workflow predates the event-driven architecture and is shared by all SDO types. + // TODO: Migrate to an event-driven pattern for consistency with the conversion workflows. const now = new Date().toISOString(); const revokedByRelationship = await relationshipsService.create( { @@ -891,6 +898,7 @@ class BaseService extends ServiceWithHooks { }, { userAccountId: options.userAccountId }, ); + result.addCreated(revokedByRelationship); // TODO what if relationshipsService.create fails after we've already marked Object A as revoked? // We should have error handling to attempt to roll back the revoked status if the relationship @@ -900,10 +908,8 @@ class BaseService extends ServiceWithHooks { // We would also need to handle potential errors in that rollback attempt and log them appropriately. // ────────────────────────────────────────────── - // 6. HANDLE RELATIONSHIPS + // 6. HANDLE RELATIONSHIPS (transfer if preserveRelationships is set) // ────────────────────────────────────────────── - const relationshipsSummary = { deleted: 0, transferred: 0, warnings: [] }; - const existingRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( objectA.stix.id, ); @@ -944,8 +950,6 @@ class BaseService extends ServiceWithHooks { // Skip if Object B already has an equivalent relationship const candidateTriple = `${relData.stix.source_ref}--${relData.stix.relationship_type}--${relData.stix.target_ref}`; if (objectBRelTriples.has(candidateTriple)) { - relationshipsSummary.duplicatesSkipped = - (relationshipsSummary.duplicatesSkipped || 0) + 1; logger.info( `Skipping duplicate relationship transfer: ${candidateTriple} already exists on Object B`, ); @@ -955,18 +959,16 @@ class BaseService extends ServiceWithHooks { // Generate a new STIX ID for the cloned relationship relData.stix.id = `relationship--${uuid.v4()}`; - await relationshipsService.create(relData, { + const transferredRel = await relationshipsService.create(relData, { userAccountId: options.userAccountId, }); - relationshipsSummary.transferred++; + result.addCreated(transferredRel); // Track the newly created triple so subsequent iterations don't create duplicates objectBRelTriples.add(candidateTriple); } catch (err) { logger.warn(`Failed to transfer relationship ${rel.stix.id}: ${err.message}`); - relationshipsSummary.warnings.push( - `Failed to transfer relationship ${rel.stix.id}: ${err.message}`, - ); + result.addWarning(`Failed to transfer relationship ${rel.stix.id}: ${err.message}`); } } } @@ -982,27 +984,17 @@ class BaseService extends ServiceWithHooks { // RelationshipsService listens for revoked events and deprecates all relationships // referencing the revoked object (except those in excludeRelationshipIds). // EventBus.emit() awaits all listeners, so deprecation completes before we return. + // Handler results (deprecated docs, warnings) are merged into the WorkflowResult. const excludeRelationshipIds = [revokedByRelationship.stix.id]; - await this.emitRevokedEvent(revokedDocument, objectB, options, { excludeRelationshipIds }); - - // Count deprecated relationships via cross-service READ (allowed by architecture) - const postRevokeRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( - objectA.stix.id, - ); - relationshipsSummary.deprecated = postRevokeRelationships.filter( - (r) => r.stix.x_mitre_deprecated === true && r.stix.id !== revokedByRelationship.stix.id, - ).length; + const eventResults = await this.emitRevokedEvent(revokedDocument, objectB, options, { + excludeRelationshipIds, + }); + result.mergeEventResults(eventResults); // ────────────────────────────────────────────── // 9. RETURN RESULT // ────────────────────────────────────────────── - return { - revokedObject: revokedDocument, - revokedByRelationship: revokedByRelationship.toObject - ? revokedByRelationship.toObject() - : revokedByRelationship, - relationshipsSummary, - }; + return result.toJSON(); } // TODO rename to deleteManyByStixId diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js index ad230ba0..a691b266 100644 --- a/app/tests/api/techniques/techniques.revoke.spec.js +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -201,19 +201,21 @@ describe('Techniques Revoke API', function () { revokeResult = res.body; - // Verify the response structure - expect(revokeResult.revokedObject).toBeDefined(); - expect(revokeResult.revokedByRelationship).toBeDefined(); - expect(revokeResult.relationshipsSummary).toBeDefined(); - - // Verify the revoked object - expect(revokeResult.revokedObject.stix.id).toBe(techniqueA.stix.id); - expect(revokeResult.revokedObject.stix.revoked).toBe(true); - - // Verify the revoked-by relationship - expect(revokeResult.revokedByRelationship.stix.relationship_type).toBe('revoked-by'); - expect(revokeResult.revokedByRelationship.stix.source_ref).toBe(techniqueA.stix.id); - expect(revokeResult.revokedByRelationship.stix.target_ref).toBe(techniqueB.stix.id); + // Verify the WorkflowResult envelope + expect(revokeResult.workflow).toBe('revoke'); + expect(revokeResult.primary).toBeDefined(); + expect(revokeResult.sideEffects).toBeDefined(); + + // Verify the revoked object (primary) + expect(revokeResult.primary.stix.id).toBe(techniqueA.stix.id); + expect(revokeResult.primary.stix.revoked).toBe(true); + + // Verify the revoked-by relationship (first created side effect) + expect(revokeResult.sideEffects.created.length).toBeGreaterThan(0); + const revokedByRel = revokeResult.sideEffects.created[0]; + expect(revokedByRel.stix.relationship_type).toBe('revoked-by'); + expect(revokedByRel.stix.source_ref).toBe(techniqueA.stix.id); + expect(revokedByRel.stix.target_ref).toBe(techniqueB.stix.id); }); it('GET /api/techniques/:stixId returns the revoked technique with revoked = true', async function () { @@ -381,8 +383,9 @@ describe('Techniques Revoke API', function () { .expect('Content-Type', /json/); const result = revokeRes.body; - expect(result.relationshipsSummary.transferred).toBe(1); - expect(result.relationshipsSummary.deprecated).toBeGreaterThanOrEqual(1); + // revoked-by + 1 transferred relationship = 2 created + expect(result.sideEffects.created.length).toBe(2); + expect(result.sideEffects.deprecated.length).toBeGreaterThanOrEqual(1); // Verify the original relationship was deprecated (not deleted — history preserved) const relRes2 = await request(app) @@ -524,8 +527,9 @@ describe('Techniques Revoke API', function () { const result = revokeRes.body; // The duplicate should have been skipped, not transferred - expect(result.relationshipsSummary.transferred).toBe(0); - expect(result.relationshipsSummary.duplicatesSkipped).toBe(1); + // Only the revoked-by relationship should be in created (no transferred relationships) + expect(result.sideEffects.created.length).toBe(1); + expect(result.sideEffects.created[0].stix.relationship_type).toBe('revoked-by'); // Verify exactly one "mitigates" relationship exists from M1 → F (the pre-existing one) const relsRes = await request(app) diff --git a/docs/user/revoke-workflow.md b/docs/user/revoke-workflow.md index 7dedddcd..66794909 100644 --- a/docs/user/revoke-workflow.md +++ b/docs/user/revoke-workflow.md @@ -32,98 +32,69 @@ Specify the `id` and `modified` timestamp for the revoked object in the request Optionally, you can set the following query parameter to preserve relationships: -- `preserveRelationships` (boolean): If set to `true`, the workflow will attempt to preserve existing relationships by substituting the revoked object with the new revoked version in those relationships. If not set or set to `false`, all relationships involving the revoked object will be deleted. Notably, if the revoking object (Object B) already participates in a relationship with the same source, target, and relationship type as an existing relationship of the revoked object (Object A), that relationship will be preserved as-is without creating a new relationship for Object B. +- `preserveRelationships` (boolean): If set to `true`, the workflow clones each relationship that references the revoked object so that it points to the revoking object instead, then deprecates the original. If not set or set to `false`, relationships referencing the revoked object are deprecated without being transferred. If the revoking object (Object B) already participates in a relationship with the same source, target, and relationship type as an existing relationship of the revoked object (Object A), the transfer is skipped and a warning is included in the response. ## Response ### Success Response -On success, the API will return a 200 OK response with the following body, which includes the revoked object, the new "revoked-by" relationship, and a summary of how relationships were handled: +On success, the API returns a **200 OK** with a [workflow response envelope](../../docs/developer/workflow-response-pattern.md). The response includes the revoked object, the `revoked-by` relationship, any transferred relationships, and any deprecated relationships: ```json { - "revokedObject": { + "workflow": "revoke", + "primary": { "workspace": { - "workflow": { - "state": "work-in-progress", - "created_by_user_account": "identity--3562fbf3-795f-4955-8e64-6c964f598e1c" - }, - "attack_id": "T0006", - "collections": [], - "embedded_relationships": [] + "workflow": { "state": "work-in-progress" }, + "attack_id": "T0006" }, "stix": { "type": "attack-pattern", "spec_version": "2.1", "id": "attack-pattern--83efdc56-d35f-4508-9f10-152bbfffde79", - "created": "2026-03-27T14:31:52.711Z", - "created_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", "revoked": true, - "external_references": [ - { - "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/T0006", - "external_id": "T0006" - } - ], - "object_marking_refs": [ - "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" - ], "modified": "2026-03-27T14:31:52.744Z", - "name": "technique-E", - "description": "This technique will be revoked.", - "kill_chain_phases": [ - { - "kill_chain_name": "kill-chain-name-1", - "phase_name": "phase-1" - } - ], - "x_mitre_attack_spec_version": "3.3.0", - "x_mitre_contributors": [], - "x_mitre_deprecated": false, - "x_mitre_is_subtechnique": false, - "x_mitre_modified_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", - "x_mitre_platforms": [ - "platform-1" - ] + "name": "technique-E" } }, - "revokedByRelationship": { - "workspace": { - "workflow": { - "created_by_user_account": "identity--3562fbf3-795f-4955-8e64-6c964f598e1c" - }, - "collections": [], - "embedded_relationships": [] - }, - "stix": { - "type": "relationship", - "spec_version": "2.1", - "id": "relationship--64d45634-f855-4fea-b084-33a87858406d", - "created": "2026-03-27T14:31:52.745Z", - "created_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", - "object_marking_refs": [], - "modified": "2026-03-27T14:31:52.745Z", - "relationship_type": "revoked-by", - "source_ref": "attack-pattern--83efdc56-d35f-4508-9f10-152bbfffde79", - "target_ref": "attack-pattern--ab992c5a-4a03-4374-ad15-440fac072760", - "x_mitre_modified_by_ref": "identity--c21f0782-50a8-4e5f-87a1-b56703e78e48", - "x_mitre_attack_spec_version": "3.3.0", - "external_references": [] - }, - "_id": "69c694d8eb64093bcd182721", - "__v": 0, - "warnings": [] + "sideEffects": { + "created": [ + { + "workspace": { "workflow": {} }, + "stix": { + "type": "relationship", + "relationship_type": "revoked-by", + "source_ref": "attack-pattern--83efdc56-d35f-4508-9f10-152bbfffde79", + "target_ref": "attack-pattern--ab992c5a-4a03-4374-ad15-440fac072760" + } + } + ], + "modified": [], + "deprecated": [ + { + "workspace": { "workflow": { "state": "reviewed" } }, + "stix": { + "type": "relationship", + "relationship_type": "uses", + "x_mitre_deprecated": true + } + } + ], + "deleted": { "count": 0, "stixIds": [] } }, - "relationshipsSummary": { - "deleted": 1, - "transferred": 0, - "warnings": [], - "duplicatesSkipped": 1 - } + "warnings": [] } ``` +| Field | Description | +|-------|-------------| +| `workflow` | Always `"revoke"` | +| `primary` | The technique in its post-revocation state (`revoked: true`) | +| `sideEffects.created` | The `revoked-by` relationship, plus any transferred relationships (when `preserveRelationships=true`) | +| `sideEffects.deprecated` | Relationships that referenced the revoked object, deprecated with `x_mitre_deprecated: true` | +| `warnings` | Non-fatal issues (e.g., duplicate relationships skipped during transfer) | + +> **Note:** When `preserveRelationships=true`, relationships are cloned to point to the revoking object (appearing in `sideEffects.created`) and the originals are deprecated (appearing in `sideEffects.deprecated`). If a duplicate relationship already exists on the revoking object, the transfer is skipped and a warning is emitted instead. ### Error Responses From 8556305cc8420f51f6dc881c379e869f8122dcf5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:50:02 -0400 Subject: [PATCH 259/370] docs: add developer documentation explaining new universal workflow response pattern --- docs/developer/workflow-response-pattern.md | 246 ++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 docs/developer/workflow-response-pattern.md diff --git a/docs/developer/workflow-response-pattern.md b/docs/developer/workflow-response-pattern.md new file mode 100644 index 00000000..fac5a60f --- /dev/null +++ b/docs/developer/workflow-response-pattern.md @@ -0,0 +1,246 @@ +# Workflow Response Pattern + +## Overview + +The ATT&CK Workbench REST API exposes several backend workflow endpoints that orchestrate complex, multi-step operations in a single atomic request. Examples include revoking an object, converting a technique to a subtechnique, and converting a subtechnique to a technique. + +These workflows create, modify, deprecate, or delete multiple documents as side effects. The user needs visibility into all changes that occurred as a consequence of their request. To support this, all workflow endpoints return a universal response structure called a **Workflow Result**. + +## Response Schema + +Every workflow endpoint returns the same top-level shape: + +```json +{ + "workflow": "convert-to-subtechnique", + "primary": { + "workspace": { ... }, + "stix": { ... } + }, + "sideEffects": { + "created": [ { "workspace": { ... }, "stix": { ... } } ], + "modified": [], + "deprecated": [], + "deleted": { "count": 0, "stixIds": [] } + }, + "warnings": [] +} +``` + +### Field Reference + +| Field | Type | Description | +|-------|------|-------------| +| `workflow` | `string` | Discriminator identifying which workflow was executed. One of: `"revoke"`, `"convert-to-subtechnique"`, `"convert-to-technique"`. | +| `primary` | `object` | The main object the user acted on. Always exactly one full `workspace + stix` document. | +| `sideEffects.created` | `array` | Full documents created as consequences of the workflow (e.g., `revoked-by` relationship, `subtechnique-of` relationship). | +| `sideEffects.modified` | `array` | Full documents modified as consequences (e.g., transferred relationships). | +| `sideEffects.deprecated` | `array` | Full documents that had `x_mitre_deprecated` set to `true` as a consequence. | +| `sideEffects.deleted` | `object` | Hard-deleted documents. Only count + STIX IDs are returned (the documents no longer exist). | +| `sideEffects.deleted.count` | `integer` | Number of deleted documents. | +| `sideEffects.deleted.stixIds` | `array` | STIX IDs of deleted documents. | +| `warnings` | `array` | Non-fatal issues encountered during the workflow (e.g., a relationship that could not be deprecated). | + +**Design rationale:** Counts are derivable from array lengths, so no separate summary object is needed. The `deleted` category is the sole exception because deleted documents cannot be returned in full — only their IDs survive. + +## Which Endpoints Use This Pattern + +| Endpoint | `workflow` value | `primary` | Typical side effects | +|----------|-----------------|-----------|----------------------| +| `POST /api/:type/:stixId/revoke` | `"revoke"` | Revoked object | `created`: revoked-by relationship; `deprecated`: relationships referencing the revoked object | +| `POST /api/techniques/:stixId/convert-to-subtechnique` | `"convert-to-subtechnique"` | Converted subtechnique | `created`: subtechnique-of relationship | +| `POST /api/techniques/:stixId/convert-to-technique` | `"convert-to-technique"` | Converted technique | `deprecated`: subtechnique-of relationship(s) | + +## WorkflowResult Builder + +The `WorkflowResult` class (`app/lib/workflow-result.js`) is a DTO builder that assembles the response. Services construct a `WorkflowResult`, populate it, then return `result.toJSON()`. + +### API + +```javascript +const WorkflowResult = require('../../lib/workflow-result'); + +// Create +const result = new WorkflowResult('convert-to-subtechnique'); + +// Set the primary object +result.setPrimary(savedDocument); + +// Add side effects +result.addCreated(relationshipDoc); // single doc or array +result.addModified(transferredRelDoc); // single doc or array +result.addDeprecated(deprecatedRelDoc); // single doc or array +result.addDeleted(['attack-pattern--...']); // array of STIX IDs + +// Add warnings +result.addWarning('Could not deprecate relationship--...'); + +// Merge results returned by event handlers (see below) +const eventResults = await EventBus.emit(eventName, payload); +result.mergeEventResults(eventResults); + +// Serialize (calls .toObject() on Mongoose docs, strips _id/__v/__t) +return result.toJSON(); +``` + +### `mergeEventResults(eventResults)` + +Accepts the array returned by `EventBus.emit()` and merges each handler's result into the appropriate side-effect category: + +```javascript +mergeEventResults(eventResults) { + for (const handlerResult of eventResults) { + if (handlerResult.created) this.addCreated(handlerResult.created); + if (handlerResult.modified) this.addModified(handlerResult.modified); + if (handlerResult.deprecated) this.addDeprecated(handlerResult.deprecated); + if (handlerResult.warnings) this.addWarnings(handlerResult.warnings); + } +} +``` + +## Event Handler Return Contract + +To surface side effects in the workflow response, event handlers must return their results. + +### Before (results discarded) + +```javascript +static async handleSubtechniqueConvertedToTechnique(payload) { + // ... deprecate relationships ... + logger.info(`Deprecated ${count} relationships`); + // Returns undefined — caller has no visibility +} +``` + +### After (results returned) + +```javascript +static async handleSubtechniqueConvertedToTechnique(payload) { + // ... deprecate relationships ... + return { deprecated: deprecatedDocs }; +} +``` + +### Return Shape + +Event handlers return a plain object with any subset of these keys: + +```javascript +{ + created: [ /* full documents */ ], + modified: [ /* full documents */ ], + deprecated: [ /* full documents */ ], + warnings: [ /* string messages */ ] +} +``` + +Handlers that encounter errors in their catch blocks should return `{ warnings: [...] }` rather than swallowing the error silently: + +```javascript +} catch (error) { + logger.error(`Failed to deprecate relationship ${relId}: ${error.message}`); + return { warnings: [`Failed to deprecate relationship ${relId}`] }; +} +``` + +## EventBus: Returning Handler Results + +`EventBus.emit()` uses `Promise.allSettled` to execute listeners. It collects the fulfilled return values and returns them as an array: + +```javascript +async emit(eventName, payload) { + // ... existing logging ... + const results = await Promise.allSettled( + listeners.map(async (listener) => { + return await listener(payload); // return value preserved + }), + ); + + // Return fulfilled values (undefined returns filtered out) + return results + .filter((r) => r.status === 'fulfilled' && r.value != null) + .map((r) => r.value); +} +``` + +This is backward-compatible: callers that do not inspect the return value are unaffected. Handlers that do not return anything produce `undefined`, which is filtered out. + +## Usage Example: Convert to Subtechnique + +Full flow from service → event handler → response: + +```javascript +// In TechniquesService.convertToSubtechnique(): +const result = new WorkflowResult('convert-to-subtechnique'); + +// 1. Save the converted technique +const savedDocument = await this.repository.save(newVersion); +result.setPrimary(savedDocument); + +// 2. Emit event — RelationshipsService creates the subtechnique-of SRO +const eventResults = await EventBus.emit( + EventConstants.TECHNIQUE_CONVERTED_TO_SUBTECHNIQUE, + { stixId, parentStixId, userAccountId }, +); +result.mergeEventResults(eventResults); + +// 3. Return the assembled response +return result.toJSON(); + + +// In RelationshipsService.handleTechniqueConvertedToSubtechnique(): +static async handleTechniqueConvertedToSubtechnique(payload) { + const { stixId, parentStixId, userAccountId } = payload; + try { + const rel = await relationshipsService.create({ ... }, { userAccountId }); + return { created: [rel] }; + } catch (error) { + logger.error(`Failed to create subtechnique-of: ${error.message}`); + return { warnings: [`Failed to create subtechnique-of relationship for ${stixId}`] }; + } +} +``` + +The HTTP response body will be: + +```json +{ + "workflow": "convert-to-subtechnique", + "primary": { + "workspace": { "attack_id": "T1234.001", ... }, + "stix": { "x_mitre_is_subtechnique": true, ... } + }, + "sideEffects": { + "created": [ + { + "stix": { + "type": "relationship", + "relationship_type": "subtechnique-of", + "source_ref": "attack-pattern--...", + "target_ref": "attack-pattern--..." + } + } + ], + "modified": [], + "deprecated": [], + "deleted": { "count": 0, "stixIds": [] } + }, + "warnings": [] +} +``` + +## Adding a New Workflow: Checklist + +1. Choose a `workflow` discriminator string (e.g., `"deprecate"`). +2. In your service method: + - Create `new WorkflowResult('your-workflow-name')` + - Call `result.setPrimary(...)` after persisting the primary object + - Call `result.addCreated/addDeprecated/etc.` for any side effects performed directly + - Capture `EventBus.emit()` return value and call `result.mergeEventResults(...)` + - Return `result.toJSON()` +3. In each event handler that produces side effects: + - Return `{ created, modified, deprecated, warnings }` (include only the keys that apply) + - On error, return `{ warnings: [...] }` instead of swallowing silently +4. Add the new workflow value to the `workflow` enum in the OpenAPI schema (`app/api/definitions/components/workflow-response.yml`). +5. Reference the `workflow-response` schema in the endpoint's OpenAPI path definition. +6. Update user-facing documentation in `docs/user/`. From ba3c36e43d5c3926e2bd1750588a3f321ee8e888 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:55:38 -0400 Subject: [PATCH 260/370] fix: during revoke ops, push a warning when revoking obj already has an equiv SRO --- app/services/meta-classes/base.service.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 21c0f3ca..a02047bd 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -953,6 +953,9 @@ class BaseService extends ServiceWithHooks { logger.info( `Skipping duplicate relationship transfer: ${candidateTriple} already exists on Object B`, ); + result.addWarning( + `Skipping duplicate relationship transfer: ${candidateTriple} already exists on Object B`, + ); continue; } From cc8b560ddfa9b443e3e2540c9558f75c308bae20 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:15:41 -0400 Subject: [PATCH 261/370] fix: update adm test to trigger validation error using an unprotected field x_mitre_is_subtechnique is blocked from being edited in PUT requests, so the test would always return a 200. Instead induce a true negative condition by modifying a field that the server doesn't protect --- app/tests/middleware/adm-validation-middleware.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/middleware/adm-validation-middleware.spec.js b/app/tests/middleware/adm-validation-middleware.spec.js index 47d43425..f2fbe5a0 100644 --- a/app/tests/middleware/adm-validation-middleware.spec.js +++ b/app/tests/middleware/adm-validation-middleware.spec.js @@ -471,7 +471,7 @@ describe('ADM Validation Middleware', function () { }, stix: { ...createdObject.stix, - x_mitre_is_subtechnique: 'not-a-boolean', // Invalid type + description: true, // <-- should trigger validation error (should be string) }, }; From a9c62d227f7d964db5368289f3754649ed04aff5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:20:03 -0400 Subject: [PATCH 262/370] fix: permanently remove the /api/validate endpoint This endpoint has been superceded by the dryRun query parameter --- app/api/definitions/openapi.yml | 4 - app/api/definitions/paths/validate-paths.yml | 38 ---- app/controllers/validate-controller.js | 45 ----- app/routes/validate-routes.js | 11 -- app/services/system/validate-service.js | 187 ------------------- 5 files changed, 285 deletions(-) delete mode 100644 app/api/definitions/paths/validate-paths.yml delete mode 100644 app/controllers/validate-controller.js delete mode 100644 app/routes/validate-routes.js delete mode 100644 app/services/system/validate-service.js diff --git a/app/api/definitions/openapi.yml b/app/api/definitions/openapi.yml index 65b08ff6..e66e7b04 100644 --- a/app/api/definitions/openapi.yml +++ b/app/api/definitions/openapi.yml @@ -495,7 +495,3 @@ paths: /api/health/status: $ref: 'paths/health-paths.yml#/paths/~1api~1health~1status' - - # Validate - /api/validate: - $ref: 'paths/validate-paths.yml#/paths/~1api~1validate' diff --git a/app/api/definitions/paths/validate-paths.yml b/app/api/definitions/paths/validate-paths.yml deleted file mode 100644 index 6a81ed85..00000000 --- a/app/api/definitions/paths/validate-paths.yml +++ /dev/null @@ -1,38 +0,0 @@ -paths: - /api/validate: - post: - summary: 'Validate a STIX object' - operationId: 'validate-stix-object' - description: | - Validates a STIX object against the schema specified in the request body `type` field. - Returns detailed error information as ZodError objects. - tags: - - 'Validation' - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - type - - stix - properties: - type: - type: string - description: Valid STIX type (e.g., "attack-pattern", "intrustion-set", etc.) - status: - type: string - description: Workflow status - "work-in-progress", "awaiting-reviewed", "reviewed" - stix: - type: object - description: The STIX object to validate - responses: - '200': - description: 'Validation result' - content: - application/json: - schema: - type: object - '400': - description: 'Invalid request' diff --git a/app/controllers/validate-controller.js b/app/controllers/validate-controller.js deleted file mode 100644 index bda4efc6..00000000 --- a/app/controllers/validate-controller.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const { z } = require('zod'); -const { StatusCodes } = require('http-status-codes'); -const validateService = require('../services/system/validate-service'); -const logger = require('../lib/logger'); - -const validationRequestSchema = z.object({ - type: z.string(), - status: z.enum(['work-in-progress', 'awaiting-review', 'reviewed', 'static']), - stix: z.object({}).loose(), -}); - -/** - * Controller for validating STIX objects - * @param {Object} req - Express request object - * @param {Object} res - Express response object - */ -exports.validate = async function (req, res) { - logger.debug(req.body); - try { - // Validate request structure - const requestResult = validationRequestSchema.safeParse(req.body); - if (!requestResult.success) { - const errors = - requestResult.error.issues?.map((issue) => ({ - message: `${issue.path?.join('.') || 'root'} is ${issue.message}`, - path: issue.path, - code: issue.code, - })) || []; - logger.error('Cannot validate request body', { errors }); - return res.status(StatusCodes.BAD_REQUEST).json({ errors }); - } else { - const result = validateService.validateStixObject(req.body); - result.deprecated = true; - result.deprecationNotice = 'Use ?dryRun=true on POST/PUT endpoints instead.'; - res.json(result); - } - } catch (error) { - console.error('Validation controller error:', error); - res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - error: 'Internal Server Error', - }); - } -}; diff --git a/app/routes/validate-routes.js b/app/routes/validate-routes.js deleted file mode 100644 index c8e229f3..00000000 --- a/app/routes/validate-routes.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -const express = require('express'); - -const validateController = require('../controllers/validate-controller'); - -const router = express.Router(); - -router.route('/validate').post(validateController.validate); - -module.exports = router; diff --git a/app/services/system/validate-service.js b/app/services/system/validate-service.js deleted file mode 100644 index c42575c0..00000000 --- a/app/services/system/validate-service.js +++ /dev/null @@ -1,187 +0,0 @@ -'use strict'; -const { STIX_SCHEMAS, ERROR_TRANSFORMATION_RULES } = require('../../lib/validation-schemas'); - -/** - * Get the schema to use for validating a STIX object. - * - * Some STIX types define both a "base" schema and "checks" (refinements), - * while others only define a single schema (no refinements). This helper - * composes the correct schema based on the STIX type and workflow status. - * - * Composition order (for schemas with checks): - * base → .partial() (if WIP) → .check(checks) - * - * This ordering is critical because Zod v4.3.6+ disallows .omit(), .pick(), - * and .partial() on schemas that already have .check() applied. - * - * For WIP objects, all fields are made optional via .partial(). - * For non-WIP objects, the raw ADM schema is used as-is. Server-controlled - * field errors are handled post-validation by ERROR_TRANSFORMATION_RULES. - * - * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") - * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") - * @returns {Object|null} Zod schema, or null if the STIX type is unknown - */ -function getSchema(stixType, status) { - const admSchemaRef = STIX_SCHEMAS[stixType]; - if (!admSchemaRef) return null; - - const isWip = status === 'work-in-progress'; - - if (admSchemaRef.base && admSchemaRef.checks) { - // Schema with refinements: compose in the safe order (partial BEFORE check) - const base = isWip ? admSchemaRef.base.partial() : admSchemaRef.base; - return base.check(admSchemaRef.checks); - } - - // Simple schema (no refinements) - return isWip ? admSchemaRef.partial() : admSchemaRef; -} -exports.getSchema = getSchema; - -/** - * Check if a validation error should be transformed (converted to warning or suppressed) - * @param {Object} error - The validation error from Zod - * @param {string} stixType - The STIX type being validated - * @returns {Object|null} The rule that matches, or null if no transformation should occur - */ -function shouldTransformError(error, stixType) { - for (const rule of ERROR_TRANSFORMATION_RULES) { - // Validate that suppressError and warningMessage are mutually exclusive - if (rule.suppressError && rule.warningMessage !== undefined && rule.warningMessage !== '') { - console.warn( - 'Rule has both suppressError and warningMessage set. suppressError takes precedence.', - ); - } - - // Check if stixType matches (if specified in rule) - if (rule.stixType) { - // Handle 'all' case - if (rule.stixType === 'all') { - // Match any STIX type - } else if (Array.isArray(rule.stixType)) { - // Check if current stixType is in the array - if (!rule.stixType.includes(stixType)) { - continue; - } - } else if (rule.stixType !== stixType) { - // Single string comparison - continue; - } - } - - // Check if field path matches (if specified in rule) - if (rule.fieldPath && JSON.stringify(rule.fieldPath) !== JSON.stringify(error.path)) { - continue; - } - - // Check if error code matches (if specified in rule) - if (rule.errorCode && rule.errorCode !== error.code) { - continue; - } - - // All specified criteria match - return rule; - } - return null; -} -exports.shouldTransformError = shouldTransformError; - -/** - * Process validation issues and separate them into errors and warnings - * @param {Array} issues - Zod validation issues - * @param {string} stixType - The STIX type being validated - * @param {string} pathPrefix - Prefix to add to error paths (e.g., 'stix') - * @returns {Object} Object with errors and warnings arrays - */ -function processValidationIssues(issues, stixType, pathPrefix = '') { - const errors = []; - const warnings = []; - - (issues || []).forEach((issue) => { - const fullPath = pathPrefix ? [pathPrefix, ...issue.path] : issue.path; - const errorData = { - message: `${fullPath.join('.')} is ${issue.message}`, - path: fullPath, - code: issue.code, - input: issue.input, - }; - - const transformationRule = shouldTransformError(errorData, stixType); - - if (transformationRule) { - // Check if error should be suppressed (suppressError takes precedence) - if (transformationRule.suppressError) { - // Suppress the error entirely - don't add to errors or warnings - return; - } else if (transformationRule.warningMessage !== undefined) { - // Convert error to warning - warnings.push({ - message: transformationRule.warningMessage || errorData.message, - path: errorData.path, - code: errorData.code, - input: issue.input, - }); - } else { - // Fallback - keep as error if no valid transformation - errors.push(errorData); - } - } else { - // Keep as error - errors.push(errorData); - } - }); - - return { errors, warnings }; -} -exports.processValidationIssues = processValidationIssues; - -/** - * Validates a STIX object based on its type and status - * @param {Object} payload - The request body - * @param {string} payload.type - STIX object type - * @param {string} payload.status - Validation strictness level - * @param {Object} payload.stix - STIX object data to validate - * @returns {Object} Validation result with valid flag and errors/data - */ -exports.validateStixObject = function (payload) { - const { type, status, stix } = payload; - - // Check if STIX type is supported - const baseSchema = STIX_SCHEMAS[type]; - if (!baseSchema) { - return { - valid: false, - errors: [ - { - message: `Unknown STIX type: ${type}`, - path: ['type'], - code: 'custom', - input: type, - }, - ], - }; - } - - // Get the schema to run - const stixSchema = getSchema(type, status); - - // Validate STIX data - const stixResult = stixSchema.safeParse(stix); - - if (stixResult.success) { - return { - valid: true, - data: stixResult.data, - }; - } - - // Process validation errors and separate them into errors and warnings - const { errors, warnings } = processValidationIssues(stixResult.error.issues, type, 'stix'); - - return { - valid: errors.length === 0, // Valid if no blocking errors (warnings are OK) - errors, - warnings, - }; -}; From d51a5a10dc461d2f16d734e72065f1e830fb5c56 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:42:23 -0400 Subject: [PATCH 263/370] feat: enable JIT validation bypass for ADM rules + support ATT&CK ID namespace prefix --- .../validation-bypasses-controller.js | 82 +++++++++ app/lib/attack-id-generator.js | 56 +++++-- app/lib/event-constants.js | 3 + app/lib/validation-schemas.js | 85 +++------- app/models/validation-bypass-rule-model.js | 25 +++ .../validation-bypasses-repository.js | 85 ++++++++++ app/routes/validation-bypasses-routes.js | 33 ++++ .../system/system-configuration-service.js | 7 + .../system/validation-bypasses-service.js | 157 ++++++++++++++++++ 9 files changed, 458 insertions(+), 75 deletions(-) create mode 100644 app/controllers/validation-bypasses-controller.js create mode 100644 app/models/validation-bypass-rule-model.js create mode 100644 app/repository/validation-bypasses-repository.js create mode 100644 app/routes/validation-bypasses-routes.js create mode 100644 app/services/system/validation-bypasses-service.js diff --git a/app/controllers/validation-bypasses-controller.js b/app/controllers/validation-bypasses-controller.js new file mode 100644 index 00000000..459adea9 --- /dev/null +++ b/app/controllers/validation-bypasses-controller.js @@ -0,0 +1,82 @@ +'use strict'; + +const validationBypassesService = require('../services/system/validation-bypasses-service'); +const logger = require('../lib/logger'); +const { DuplicateIdError } = require('../exceptions'); + +exports.retrieveAll = async function (req, res) { + const options = { + offset: req.query.offset || 0, + limit: req.query.limit || 0, + includePagination: req.query.includePagination, + }; + + try { + const results = await validationBypassesService.retrieveAll(options); + if (options.includePagination) { + logger.debug( + `Success: Retrieved ${results.data.length} of ${results.pagination.total} total validation bypass rule(s)`, + ); + } else { + logger.debug(`Success: Retrieved ${results.length} validation bypass rule(s)`); + } + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get validation bypass rules. Server error.'); + } +}; + +exports.create = async function (req, res) { + const data = req.body; + + if (!data.fieldPath || !data.errorCode || !data.stixType) { + return res + .status(400) + .send( + 'Unable to create validation bypass rule. Missing required properties (fieldPath, errorCode, stixType).', + ); + } + + try { + const rule = await validationBypassesService.create(data); + logger.debug('Success: Created validation bypass rule with id ' + rule._id); + return res.status(201).send(rule); + } catch (err) { + if (err instanceof DuplicateIdError) { + logger.warn('Duplicate validation bypass rule'); + return res.status(409).send('Unable to create validation bypass rule. Duplicate rule.'); + } else { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to create validation bypass rule. Server error.'); + } + } +}; + +exports.retrieveById = async function (req, res) { + try { + const rule = await validationBypassesService.retrieveById(req.params.id); + if (!rule) { + return res.status(404).send('Validation bypass rule not found.'); + } + logger.debug('Success: Retrieved validation bypass rule with id ' + req.params.id); + return res.status(200).send(rule); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get validation bypass rule. Server error.'); + } +}; + +exports.deleteById = async function (req, res) { + try { + const rule = await validationBypassesService.deleteById(req.params.id); + if (!rule) { + return res.status(404).send('Validation bypass rule not found.'); + } + logger.debug('Success: Deleted validation bypass rule with id ' + req.params.id); + return res.status(204).end(); + } catch (err) { + logger.error('Delete validation bypass rule failed. ' + err); + return res.status(500).send('Unable to delete validation bypass rule. Server error.'); + } +}; diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js index 38aca2c7..cc151b7c 100644 --- a/app/lib/attack-id-generator.js +++ b/app/lib/attack-id-generator.js @@ -8,6 +8,21 @@ const { const { InvalidTypeError, DuplicateIdError } = require('../exceptions'); const logger = require('./logger'); const config = require('../config/config'); +const systemConfigurationRepository = require('../repository/system-configurations-repository'); + +/** + * Retrieve the organization namespace configuration (if set) + * @returns {Promise<{prefix: string, range_start: number}|null>} The namespace config or null + * @private + */ +async function getOrganizationNamespace() { + const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); + const ns = systemConfig?.organization_namespace; + if (ns?.prefix) { + return { prefix: ns.prefix.toUpperCase(), rangeStart: ns.range_start || 1000 }; + } + return null; +} /** * Determines if a given ATT&CK object type requires an ATT&CK ID. @@ -135,6 +150,14 @@ async function generateAttackId( throw new InvalidTypeError(`STIX type '${stixType}' does not support ATT&CK ID generation`); } + // Retrieve namespace configuration + const namespace = await getOrganizationNamespace(); + if (namespace) { + logger.debug( + `Using organization namespace: prefix=${namespace.prefix}, rangeStart=${namespace.rangeStart}`, + ); + } + // Handle subtechnique generation if (isSubtechnique) { if (stixType !== 'attack-pattern') { @@ -144,10 +167,10 @@ async function generateAttackId( throw new Error('Parent technique ATT&CK ID is required for subtechnique generation'); } - // Validate parent ID format (must be T####) - if (!/^T\d{4}$/.test(parentTechniqueAttackId)) { + // Validate parent ID format (must be T#### or PREFIX-T####) + if (!/^([A-Z]+-)?T\d{4}$/.test(parentTechniqueAttackId)) { throw new Error( - `Invalid parent technique ATT&CK ID format: ${parentTechniqueAttackId}. Must be T####`, + `Invalid parent technique ATT&CK ID format: ${parentTechniqueAttackId}. Must be T#### or PREFIX-T####`, ); } @@ -187,7 +210,7 @@ async function generateAttackId( const nextNum = existingSubtechniqueNumbers.length > 0 ? Math.max(...existingSubtechniqueNumbers) + 1 : 1; - // Construct new subtechnique ID (e.g., "T1234.001") + // Construct new subtechnique ID (e.g., "T1234.001" or "FOOBAR-T1234.001") const generatedId = `${parentTechniqueAttackId}.${nextNum.toString().padStart(3, '0')}`; logger.debug(`Generated subtechnique ID: ${generatedId}`); @@ -199,7 +222,10 @@ async function generateAttackId( const attackIdType = stixTypeToAttackIdMapping[stixType]; const typePrefix = getTypePrefix(attackIdType); - logger.debug(`Generating ATT&CK ID for STIX type: ${stixType}, prefix: ${typePrefix}`); + // Build the full prefix including namespace (e.g., "FOOBAR-T" or just "T") + const fullPrefix = namespace ? `${namespace.prefix}-${typePrefix}` : typePrefix; + + logger.debug(`Generating ATT&CK ID for STIX type: ${stixType}, prefix: ${fullPrefix}`); // Get all existing objects of this type // Repository returns: [{ totalCount: [...], documents: [...] }] @@ -208,29 +234,31 @@ async function generateAttackId( logger.debug(`Retrieved ${allObjects.length} objects from repository`); - // Extract numeric IDs from workspace.attack_id that match our type prefix + // Extract numeric IDs from workspace.attack_id that match our full prefix const existingIds = allObjects .map((obj) => { const attackId = obj.workspace?.attack_id; - if (!attackId || !attackId.startsWith(typePrefix)) { + if (!attackId || !attackId.startsWith(fullPrefix)) { return null; } - // Remove prefix and any decimal parts (for subtechniques) - const idWithoutPrefix = attackId.replace(typePrefix, '').replace(/\.(\d{3})$/, ''); + // Remove full prefix and any decimal parts (for subtechniques) + const idWithoutPrefix = attackId.slice(fullPrefix.length).replace(/\.(\d{3})$/, ''); const numericPart = parseInt(idWithoutPrefix, 10); return isNaN(numericPart) ? null : numericPart; }) .filter((id) => id !== null); - logger.debug(`Found ${existingIds.length} existing IDs with prefix ${typePrefix}`); + logger.debug(`Found ${existingIds.length} existing IDs with prefix ${fullPrefix}`); - // Calculate next available ID (start at 1 if none exist) - const nextId = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1; + // Calculate next available ID + // When namespace is configured, start from range_start; otherwise start at 1 + const minId = namespace ? namespace.rangeStart : 1; + const nextId = existingIds.length > 0 ? Math.max(minId, Math.max(...existingIds) + 1) : minId; - // Construct new ID with proper padding (e.g., "G0042", "TA0001", "T1234") - const generatedId = `${typePrefix}${nextId.toString().padStart(4, '0')}`; + // Construct new ID with proper padding (e.g., "G0042", "FOOBAR-T1000") + const generatedId = `${fullPrefix}${nextId.toString().padStart(4, '0')}`; logger.debug(`Generated ATT&CK ID: ${generatedId}`); diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index 932c4460..97d67565 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -151,4 +151,7 @@ module.exports = Object.freeze({ // Collection - Objects relationship COLLECTION_OBJECTS_CHANGED: 'x-mitre-collection::objects-changed', + + // System Configuration + SYSTEM_CONFIGURATION_NAMESPACE_CHANGED: 'system-configuration::namespace-changed', }); diff --git a/app/lib/validation-schemas.js b/app/lib/validation-schemas.js index 902824af..266cdb41 100644 --- a/app/lib/validation-schemas.js +++ b/app/lib/validation-schemas.js @@ -103,71 +103,34 @@ const STIX_SCHEMAS = { }; /** - * Configuration for transforming validation errors (to warnings or suppression). - * These rules handle errors produced by ADM schemas for server-controlled fields that - * clients cannot or should not set. They are used by both the validate endpoint and the - * service layer's post-composition validation. + * Get the schema to use for validating a STIX object. * - * On a fully-composed object (service layer), suppression rules naturally don't fire - * because server-controlled fields are already populated — no missing-field errors occur. - * On a pre-composed object (validate endpoint), suppression rules fire for fields - * the server will generate, preventing false negatives. + * Some STIX types define both a "base" schema and "checks" (refinements), + * while others only define a single schema (no refinements). This helper + * composes the correct schema based on the STIX type and workflow status. * - * Rule schema: - * fieldPath - Zod error path to match (e.g., ['stix', 'x_mitre_attack_spec_version']) - * errorCode - Zod error code to match (e.g., 'invalid_type', 'invalid_value') - * stixType - Which STIX types: a string, an array, or 'all' - * suppressError - If true, the error is silently dropped - * warningMessage - If set (and suppressError is falsy), convert to warning with this message - * status - (Optional, future use) Which workflow states the rule applies to + * Composition order (for schemas with checks): + * base → .partial() (if WIP) → .check(checks) + * + * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") + * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") + * @returns {Object|null} Zod schema, or null if the STIX type is unknown */ -const ERROR_TRANSFORMATION_RULES = [ - // Server always sets x_mitre_attack_spec_version - { - fieldPath: ['x_mitre_attack_spec_version'], - errorCode: 'invalid_type', - stixType: 'all', - suppressError: true, - }, - // Server sets x_mitre_modified_by_ref based on authenticated user - user does not need to supply it - { - fieldPath: ['x_mitre_modified_by_ref'], - errorCode: 'invalid_value', - stixType: 'all', - suppressError: true, - }, - // Warn about non-standard tactic shortnames - { - fieldPath: ['x_mitre_shortname'], - errorCode: 'invalid_value', - stixType: 'x-mitre-tactic', - warningMessage: - 'Tactic shortname does not match predefined ATT&CK tactics. This may prevent compatibility with official ATT&CK data but can be used for custom taxonomies.', - }, - // Server sets x_mitre_domains for certain types (assigned during bundle export) - { - fieldPath: ['x_mitre_domains'], - errorCode: 'invalid_type', - stixType: ['intrusion-set', 'campaign', 'x-mitre-matrix', 'x-mitre-detection-strategy'], - suppressError: true, - }, - // Server sets object_marking_refs for certain types - { - fieldPath: ['object_marking_refs'], - errorCode: 'invalid_type', - stixType: ['campaign', 'identity'], - suppressError: true, - }, - // Server sets created_by_ref for certain types - { - fieldPath: ['created_by_ref'], - errorCode: 'invalid_type', - stixType: ['campaign', 'x-mitre-matrix', 'x-mitre-asset', 'course-of-action'], - suppressError: true, - }, -]; +function getSchema(stixType, status) { + const admSchemaRef = STIX_SCHEMAS[stixType]; + if (!admSchemaRef) return null; + + const isWip = status === 'work-in-progress'; + + if (admSchemaRef.base && admSchemaRef.checks) { + const base = isWip ? admSchemaRef.base.partial() : admSchemaRef.base; + return base.check(admSchemaRef.checks); + } + + return isWip ? admSchemaRef.partial() : admSchemaRef; +} module.exports = { STIX_SCHEMAS, - ERROR_TRANSFORMATION_RULES, + getSchema, }; diff --git a/app/models/validation-bypass-rule-model.js b/app/models/validation-bypass-rule-model.js new file mode 100644 index 00000000..3509a92b --- /dev/null +++ b/app/models/validation-bypass-rule-model.js @@ -0,0 +1,25 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const validationBypassRuleDefinition = { + fieldPath: { type: [String], required: true }, + errorCode: { type: String, required: true }, + stixType: { type: String, required: true }, + suppressError: { type: Boolean, default: true }, + autoCreated: { type: Boolean, default: false }, +}; + +const validationBypassRuleSchema = new mongoose.Schema(validationBypassRuleDefinition, { + bufferCommands: false, +}); + +// Prevent duplicate rules for the same field/code/type combination +validationBypassRuleSchema.index({ fieldPath: 1, errorCode: 1, stixType: 1 }, { unique: true }); + +const ValidationBypassRuleModel = mongoose.model( + 'ValidationBypassRule', + validationBypassRuleSchema, +); + +module.exports = ValidationBypassRuleModel; diff --git a/app/repository/validation-bypasses-repository.js b/app/repository/validation-bypasses-repository.js new file mode 100644 index 00000000..714384e8 --- /dev/null +++ b/app/repository/validation-bypasses-repository.js @@ -0,0 +1,85 @@ +'use strict'; + +const ValidationBypassRule = require('../models/validation-bypass-rule-model'); +const { DuplicateIdError, DatabaseError } = require('../exceptions'); + +class ValidationBypassesRepository { + constructor(model) { + this.model = model; + } + + async retrieveAll(options) { + const aggregation = [{ $sort: { stixType: 1 } }]; + + const totalCount = await this.model.aggregate(aggregation).count('totalCount').exec(); + + if (options.offset) { + aggregation.push({ $skip: options.offset }); + } else { + aggregation.push({ $skip: 0 }); + } + + if (options.limit) { + aggregation.push({ $limit: options.limit }); + } + + const documents = await this.model.aggregate(aggregation).exec(); + + return [ + { + totalCount: [{ totalCount: totalCount[0]?.totalCount || 0 }], + documents: documents, + }, + ]; + } + + async save(data) { + const document = new this.model(data); + try { + return await document.save(); + } catch (err) { + if (err.name === 'MongoServerError' && err.code === 11000) { + throw new DuplicateIdError({ + details: + 'A validation bypass rule with this fieldPath, errorCode, and stixType already exists.', + }); + } else { + throw new DatabaseError(err); + } + } + } + + async retrieveById(id) { + try { + return await this.model.findById(id).lean().exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async deleteById(id) { + try { + return await this.model.findByIdAndDelete(id).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async deleteAutoCreated() { + try { + return await this.model.deleteMany({ autoCreated: true }).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + + async findAll() { + try { + return await this.model.find({}).lean().exec(); + } catch (err) { + throw new DatabaseError(err); + } + } +} + +module.exports = new ValidationBypassesRepository(ValidationBypassRule); diff --git a/app/routes/validation-bypasses-routes.js b/app/routes/validation-bypasses-routes.js new file mode 100644 index 00000000..56fd44b4 --- /dev/null +++ b/app/routes/validation-bypasses-routes.js @@ -0,0 +1,33 @@ +'use strict'; + +const express = require('express'); + +const validationBypassesController = require('../controllers/validation-bypasses-controller'); +const authn = require('../lib/authn-middleware'); +const authz = require('../lib/authz-middleware'); + +const router = express.Router(); + +router + .route('/config/validation-bypasses') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + validationBypassesController.retrieveAll, + ) + .post(authn.authenticate, authz.requireRole(authz.admin), validationBypassesController.create); + +router + .route('/config/validation-bypasses/:id') + .get( + authn.authenticate, + authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), + validationBypassesController.retrieveById, + ) + .delete( + authn.authenticate, + authz.requireRole(authz.admin), + validationBypassesController.deleteById, + ); + +module.exports = router; diff --git a/app/services/system/system-configuration-service.js b/app/services/system/system-configuration-service.js index 7d8362c3..0d9f8d31 100644 --- a/app/services/system/system-configuration-service.js +++ b/app/services/system/system-configuration-service.js @@ -246,6 +246,13 @@ class SystemConfigurationService extends BaseService { systemConfig.organization_namespace = namespace; await this.repository.constructor.saveDocument(systemConfig); + + // Emit event so ValidationBypassesService can manage its own bypass rules + const EventBus = require('../../lib/event-bus'); + const Events = require('../../lib/event-constants'); + await EventBus.emit(Events.SYSTEM_CONFIGURATION_NAMESPACE_CHANGED, { + namespace, + }); } /** diff --git a/app/services/system/validation-bypasses-service.js b/app/services/system/validation-bypasses-service.js new file mode 100644 index 00000000..79766193 --- /dev/null +++ b/app/services/system/validation-bypasses-service.js @@ -0,0 +1,157 @@ +'use strict'; + +const { BaseService } = require('../meta-classes'); +const validationBypassesRepository = require('../../repository/validation-bypasses-repository'); +const logger = require('../../lib/logger'); + +class ValidationBypassesService { + constructor(repository) { + this.repository = repository; + } + + static initializeEventListeners() { + const EventBus = require('../../lib/event-bus'); + const Events = require('../../lib/event-constants'); + + EventBus.on( + Events.SYSTEM_CONFIGURATION_NAMESPACE_CHANGED, + ValidationBypassesService.handleNamespaceChanged.bind(ValidationBypassesService), + ); + + EventBus.on( + Events.VALIDATION_BYPASS_CHECK_REQUESTED, + ValidationBypassesService.handleBypassCheckRequested.bind(ValidationBypassesService), + ); + + logger.info('ValidationBypassesService: Event listeners initialized'); + } + + /** + * Handle namespace configuration changes. + * Removes previous auto-created rules and creates new ones if a prefix is set. + * @param {Object} payload - Event payload + * @param {Object} payload.namespace - The new namespace configuration ({ prefix, range_start }) + */ + static async handleNamespaceChanged(payload) { + const { namespace } = payload; + const service = module.exports; + + // Always remove previous auto-created rules + await service.removeNamespaceRules(); + + // If a namespace prefix is being set, create new bypass rules + if (namespace?.prefix) { + const { stixTypeToAttackIdMapping } = require('@mitre-attack/attack-data-model'); + const stixTypes = Object.keys(stixTypeToAttackIdMapping); + await service.createNamespaceRules(stixTypes); + } + } + + /** + * Handle a validation bypass check request from the event bus. + * Returns an array of non-bypassed errors. + * @param {Object} payload + * @param {Array} payload.errors - Validation errors to check + * @param {string} payload.stixType - The STIX type being validated + * @returns {Promise} Non-bypassed errors + */ + static async handleBypassCheckRequested(payload) { + const { errors, stixType } = payload; + const service = module.exports; + + const nonBypassed = []; + for (const error of errors) { + const bypassed = await service.isErrorBypassed(error, stixType); + if (!bypassed) { + nonBypassed.push(error); + } + } + return nonBypassed; + } + + async retrieveAll(options) { + const results = await this.repository.retrieveAll(options); + return BaseService.paginate(options, results); + } + + async create(data) { + return await this.repository.save(data); + } + + async retrieveById(id) { + return await this.repository.retrieveById(id); + } + + async deleteById(id) { + return await this.repository.deleteById(id); + } + + /** + * Check if a validation error should be bypassed based on stored rules. + * @param {Object} error - The validation error ({ path, code, ... }) + * @param {string} stixType - The STIX type being validated + * @returns {Promise} True if the error should be bypassed + */ + async isErrorBypassed(error, stixType) { + const rules = await this.repository.findAll(); + + const errorPathStr = JSON.stringify(error.path.map(String)); + + for (const rule of rules) { + if (!rule.suppressError) continue; + + // Check stixType match ('all' matches any type) + if (rule.stixType !== 'all' && rule.stixType !== stixType) continue; + + // Check errorCode match + if (rule.errorCode !== error.code) continue; + + // Check fieldPath match (coerce both sides to string for numeric index comparison) + const rulePathStr = JSON.stringify(rule.fieldPath.map(String)); + if (rulePathStr !== errorPathStr) continue; + + return true; + } + + return false; + } + + /** + * Create bypass rules for namespace-prefixed ATT&CK IDs across all relevant STIX types. + * @param {string[]} stixTypes - STIX types that support ATT&CK IDs + */ + async createNamespaceRules(stixTypes) { + const rules = stixTypes.map((stixType) => ({ + fieldPath: ['external_references', '0', 'external_id'], + errorCode: 'custom', + stixType, + suppressError: true, + autoCreated: true, + })); + + for (const rule of rules) { + try { + await this.repository.save(rule); + } catch (err) { + // Skip duplicates — rule may already exist + if (err.name === 'DuplicateIdError') continue; + throw err; + } + } + + logger.info(`Created ${rules.length} namespace validation bypass rules`); + } + + /** + * Remove all auto-created bypass rules (e.g., when namespace is cleared). + */ + async removeNamespaceRules() { + const result = await this.repository.deleteAutoCreated(); + logger.info(`Removed ${result.deletedCount} auto-created validation bypass rules`); + } +} + +const service = new ValidationBypassesService(validationBypassesRepository); +ValidationBypassesService.initializeEventListeners(); + +module.exports = service; From a4ead5b2fa198a03d2e6d973c29c3c3975656e52 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:44:54 -0400 Subject: [PATCH 264/370] refactor: update BaseService to use event-bus pattern for preflight validation check --- app/lib/event-constants.js | 3 ++ app/services/meta-classes/base.service.js | 38 ++++++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index 97d67565..73346de3 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -154,4 +154,7 @@ module.exports = Object.freeze({ // System Configuration SYSTEM_CONFIGURATION_NAMESPACE_CHANGED: 'system-configuration::namespace-changed', + + // Validation + VALIDATION_BYPASS_CHECK_REQUESTED: 'validation-bypass::check-requested', }); diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index a02047bd..d9016fc9 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -22,7 +22,7 @@ const { AlreadyRevokedError, SelfRevocationError, } = require('../../exceptions'); -const { getSchema, processValidationIssues } = require('../system/validate-service'); +const { getSchema } = require('../../lib/validation-schemas'); const ServiceWithHooks = require('./hooks.service'); const WorkflowResult = require('../../lib/workflow-result'); @@ -398,15 +398,12 @@ class BaseService extends ServiceWithHooks { * * This runs AFTER all server-controlled fields have been populated (external_references, * x_mitre_attack_spec_version, created_by_ref, etc.) and BEFORE the repository save. - * Because the object is fully composed, the raw ADM schema validates cleanly — - * ERROR_TRANSFORMATION_RULES suppression rules naturally don't fire since the - * server-controlled fields are present. Only warning rules (e.g., x_mitre_shortname) - * may apply. + * Validation errors that match a stored bypass rule are filtered out. * * @param {Object} data - The composed request data ({ stix, workspace }) - * @returns {{ errors: Array, warnings: Array }} Validation results + * @returns {Promise<{ errors: Array, warnings: Array }>} Validation results */ - validateComposedObject(data) { + async validateComposedObject(data) { const empty = { errors: [], warnings: [] }; if (!config.validateRequests.withAttackDataModel) return empty; @@ -419,7 +416,26 @@ class BaseService extends ServiceWithHooks { const result = schema.safeParse(data.stix); if (result.success) return empty; - return processValidationIssues(result.error.issues, stixType); + // Convert Zod issues to error objects + const allErrors = result.error.issues.map((issue) => ({ + message: `${issue.path.join('.')} is ${issue.message}`, + path: issue.path, + code: issue.code, + input: issue.input, + })); + + // Filter out bypassed errors via the event bus + const EventBus = require('../../lib/event-bus'); + const Events = require('../../lib/event-constants'); + const results = await EventBus.emit(Events.VALIDATION_BYPASS_CHECK_REQUESTED, { + errors: allErrors, + stixType, + }); + + // The handler returns the non-bypassed errors array + const errors = results?.[0] ?? allErrors; + + return { errors, warnings: [] }; } /** @@ -584,7 +600,7 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 5. VALIDATE WITH ADM // ────────────────────────────────────────────── - const { errors, warnings } = this.validateComposedObject(data); + const { errors, warnings } = await this.validateComposedObject(data); if (errors.length > 0) { throw new ValidationError('ADM validation failed', { details: errors, warnings }); @@ -625,7 +641,7 @@ class BaseService extends ServiceWithHooks { data.workspace.attack_id = attackIdInExternalReferences; } - const { errors, warnings } = this.validateComposedObject(data); + const { errors, warnings } = await this.validateComposedObject(data); if (errors.length > 0) { throw new ValidationError('ADM validation failed', { details: errors, warnings }); @@ -724,7 +740,7 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 5. VALIDATE WITH ADM // ────────────────────────────────────────────── - const { errors, warnings } = this.validateComposedObject(data); + const { errors, warnings } = await this.validateComposedObject(data); if (errors.length > 0) { throw new ValidationError('ADM validation failed', { details: errors, warnings }); From ab08b87e5bacd68393430cb2a285375112a602b7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:46:06 -0400 Subject: [PATCH 265/370] docs: update developer documentation to reflect new restrictions on cross-service read operations --- docs/developer/cross-service-reads-pattern.md | 288 ++++++------------ docs/developer/event-bus-architecture.md | 45 ++- 2 files changed, 116 insertions(+), 217 deletions(-) diff --git a/docs/developer/cross-service-reads-pattern.md b/docs/developer/cross-service-reads-pattern.md index a487faf6..d238a20a 100644 --- a/docs/developer/cross-service-reads-pattern.md +++ b/docs/developer/cross-service-reads-pattern.md @@ -1,245 +1,153 @@ -# Cross-Service Reads in Event-Driven Architecture +# Cross-Service Communication Pattern -## The Dilemma +## Core Principle -When building embedded relationships (denormalized metadata cache), services need information from documents managed by other services. For example: +All cross-service communication — both reads and writes — MUST go through the EventBus. -```javascript -// DetectionStrategiesService needs analytic metadata -data.workspace.embedded_relationships.push({ - stix_id: analyticId, - attack_id: analytic?.workspace?.attack_id, // From AnalyticsService - name: analytic.stix.name, // From AnalyticsService - direction: 'outbound', -}); -``` - -**Question:** Does fetching this metadata violate the event-driven architecture principle? - -## Answer: No - Reads Are Different From Writes +| Operation | Allowed? | Pattern | +|-----------|----------|---------| +| Service A reads from Service B's repository | ❌ NO | Use events instead | +| Service A writes to Service B's repository | ❌ NO | Use events instead | +| Service A emits event → Service B reads/writes its own data and returns results | ✅ YES | Event-driven pattern | -### Core Principle Refinement +## Why Cross-Service Reads Are No Longer Permitted -The event-driven architecture aims to eliminate **cross-service WRITES**, not reads. +Previously, direct cross-service reads were allowed because `EventBus.emit()` discarded +handler return values, making it impossible to request data from another service over the bus. -| Operation | Allowed? | Rationale | -|-----------|----------|-----------| -| Service A reads from Service B's repository | ✅ YES | Safe, idempotent, necessary for denormalization | -| Service A writes to Service B's repository | ❌ NO | Violates ownership, creates tight coupling | -| Service A emits event → Service B writes to its own repository | ✅ YES | Event-driven pattern, maintains boundaries | +With the updated EventBus (commit `9b62521`), `emit()` now collects and returns fulfilled +handler values via `Promise.allSettled`. This means any service can request data from another +service by emitting an event and receiving the result — eliminating the need for the +cross-service reads exception. -### Why Cross-Service Reads Are Acceptable +**Benefits of routing reads through events:** -1. **Reads don't violate ownership** - AnalyticsService still owns analytics data; DetectionStrategiesService is just observing it - -2. **Reads don't create consistency issues** - No competing writes, no transaction boundaries to manage - -3. **Reads are necessary for denormalization** - Embedded relationships are a materialized view requiring source data - -4. **Reads don't create temporal coupling** - If analytics don't exist yet, handle gracefully with null values +1. **Uniform boundary enforcement** — one rule (use events) instead of two (events for writes, direct access for reads) +2. **Traceability** — all cross-service interactions are visible in the event log +3. **Decoupling** — services don't import each other's repositories or modules +4. **Testability** — event handlers are easier to mock than scattered repository imports ## Design Patterns -### ✅ Pattern: Read During Denormalization +### ✅ Pattern: Request Data via Event -**When to use:** Building cached metadata (embedded_relationships, computed fields) +**When to use:** A service needs data owned by another service (validation, denormalization, etc.) ```javascript -class DetectionStrategiesService extends BaseService { - async beforeCreate(data) { - // ALLOWED: Reading analytics metadata to denormalize - for (const analyticId of data.stix.x_mitre_analytic_refs) { - try { - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - data.workspace.embedded_relationships.push({ - stix_id: analyticId, - attack_id: analytic?.workspace?.attack_id || null, - name: analytic.stix.name, - direction: 'outbound', - }); - } catch (error) { - // Handle missing analytics gracefully - data.workspace.embedded_relationships.push({ - stix_id: analyticId, - attack_id: null, - name: null, - direction: 'outbound', - }); - } - } - } +// BaseService needs to check validation bypass rules (owned by ValidationBypassesService) +const EventBus = require('../../lib/event-bus'); +const Events = require('../../lib/event-constants'); - async afterCreate(document) { - // REQUIRED: Emit event so AnalyticsService can update its own documents - await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { - detectionStrategy: document, - analyticIds: document.stix.x_mitre_analytic_refs - }); - } -} -``` +const results = await EventBus.emit(Events.VALIDATION_BYPASS_CHECK_REQUESTED, { + errors: allErrors, + stixType, +}); -### ✅ Pattern: Event-Driven Writes +const filteredErrors = results?.[0] ?? allErrors; +``` -**When to use:** Updating documents managed by another service +The owning service registers a handler that returns the requested data: ```javascript -class AnalyticsService extends BaseService { +class ValidationBypassesService { static initializeEventListeners() { + const EventBus = require('../../lib/event-bus'); + const Events = require('../../lib/event-constants'); + EventBus.on( - 'x-mitre-detection-strategy::analytics-referenced', - this.handleAnalyticsReferenced.bind(this) + Events.VALIDATION_BYPASS_CHECK_REQUESTED, + ValidationBypassesService.handleBypassCheckRequested.bind(ValidationBypassesService), ); } - static async handleAnalyticsReferenced(payload) { - const { detectionStrategy, analyticIds } = payload; - - for (const analyticId of analyticIds) { - // CORRECT: AnalyticsService modifying its own documents - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - - analytic.workspace.embedded_relationships.push({ - stix_id: detectionStrategy.stix.id, - attack_id: detectionStrategy.workspace?.attack_id, - name: detectionStrategy.stix.name, - direction: 'inbound', - }); - - await analyticsRepository.saveDocument(analytic); - } + static async handleBypassCheckRequested(payload) { + const { errors, stixType } = payload; + // ... filter errors using own repository ... + return nonBypassedErrors; } } ``` -### ❌ Anti-Pattern: Direct Cross-Service Write +### ✅ Pattern: Denormalize via Event -**Never do this:** +**When to use:** Building cached metadata (embedded_relationships, computed fields) ```javascript class DetectionStrategiesService extends BaseService { - async afterCreate(document) { - // WRONG: DetectionStrategiesService directly modifying analytics - for (const analyticId of document.stix.x_mitre_analytic_refs) { - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - - analytic.workspace.embedded_relationships.push({ - stix_id: document.stix.id, - direction: 'inbound', - }); + async beforeCreate(data, options) { + // Request analytic metadata via event + const results = await EventBus.emit('x-mitre-analytic::metadata-requested', { + analyticIds: data.stix.x_mitre_analytic_refs, + }); - await analyticsRepository.saveDocument(analytic); // ❌ WRONG! + const analyticsMetadata = results?.[0] ?? []; + for (const meta of analyticsMetadata) { + data.workspace.embedded_relationships.push({ + stix_id: meta.stixId, + attack_id: meta.attackId, + name: meta.name, + direction: 'outbound', + }); } } -} -``` -**Why this is wrong:** -- Violates single responsibility (DetectionStrategiesService shouldn't know how to update analytics) -- Creates tight coupling -- Makes testing harder -- Harder to trace who modified what - -## When Should You Denormalize? - -Denormalization (embedded_relationships) is appropriate when: - -1. **UI needs the data frequently** - Avoids N+1 query problems -2. **Data changes infrequently** - Analytics names don't change often -3. **Staleness is acceptable** - If analytic name changes, embedded relationship name will be stale until next update -4. **Read performance matters** - Joining at query time would be too slow - -If these don't apply, consider storing only IDs and joining at query time. - -## Alternatives Considered - -### Alternative 1: No Denormalization (Store Only IDs) - -```javascript -// Minimal approach -data.workspace.embedded_relationships.push({ - stix_id: analyticId, - direction: 'outbound', -}); - -// Fetch names at query time -const analytics = await Promise.all( - embeddedRels.map(rel => analyticsRepository.retrieveLatestByStixId(rel.stix_id)) -); + async afterCreate(document, options) { + // Emit event so AnalyticsService can update its own documents + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { + detectionStrategy: document, + analyticIds: document.stix.x_mitre_analytic_refs, + }); + } +} ``` -**Trade-offs:** -- ✅ No cross-service reads needed -- ✅ Always current data -- ❌ Slower API responses (N+1 queries) -- ❌ More complex query logic +### ❌ Anti-Pattern: Direct Cross-Service Import -### Alternative 2: Event-Based Metadata Request (Over-Engineering) +**Never do this:** ```javascript -// Request metadata via events -const metadata = await EventBus.emitAndWait('analytic::metadata-requested', { - analyticIds -}); +class BaseService { + async validateComposedObject(data) { + // WRONG: Direct import of another service + const validationBypassesService = require('../system/validation-bypasses-service'); + const bypassed = await validationBypassesService.isErrorBypassed(error, stixType); + } +} ``` -**Trade-offs:** -- ❌ Turns events into synchronous RPC (defeats purpose) -- ❌ Adds latency and complexity -- ❌ Just hiding the read behind an event facade - -**Verdict:** Not recommended - you're just wrapping a read with more abstraction - -## Recommendations - -### For DetectionStrategiesService - -1. ✅ **Keep the cross-repository read in `beforeCreate`** - This is correct and necessary -2. ✅ **Emit events in `afterCreate`** - Let AnalyticsService update its own documents -3. ✅ **Handle missing analytics gracefully** - Store null values if analytic doesn't exist - -### For Documentation - -Update [event-bus-architecture.md](event-bus-architecture.md) to clarify: - -```markdown -### Cross-Service Communication Rules +**Why this is wrong:** +- Creates a hidden dependency between services +- Not visible in the event log +- Harder to test (must mock the imported module) +- Violates the single communication channel principle -**WRITES - Use Events:** -- ❌ Service A MUST NOT directly modify Service B's documents -- ✅ Service A emits event → Service B modifies its own documents +### ❌ Anti-Pattern: Direct Cross-Service Repository Read -**READS - Direct Repository Access Allowed:** -- ✅ Service A MAY read from Service B's repository -- Use case: Building denormalized metadata caches -- Use case: Validation (checking if referenced document exists) -- Must handle missing documents gracefully +**Never do this:** -**Example:** ```javascript -// CORRECT: Read to denormalize, emit to write -async beforeCreate(data) { - const analytic = await analyticsRepository.retrieveLatestByStixId(id); // ✅ READ - data.workspace.embedded_relationships.push({ name: analytic.stix.name }); -} - -async afterCreate(document) { - await EventBus.emit('analytics-referenced', { ... }); // ✅ EVENT FOR WRITES +class DetectionStrategiesService extends BaseService { + async beforeCreate(data) { + // WRONG: Directly reading from another service's repository + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + } } ``` -``` -## Summary +## Event Return Value Convention -**Your current implementation is correct.** The cross-repository read in `beforeCreate` is: +When emitting an event that expects a return value: -- ✅ Necessary for building denormalized metadata -- ✅ Safe (read-only operation) -- ✅ Aligned with event-driven architecture (which prohibits cross-service WRITES, not reads) -- ✅ Standard pattern in CQRS and materialized view architectures +1. **Single handler expected** — access `results[0]` +2. **Always provide a fallback** — use `results?.[0] ?? fallback` in case no handler is registered +3. **Handler returns data directly** — no wrapper objects needed; the EventBus filters out `null`/`undefined` results -**The architecture is not flawed.** The documentation just needs to clarify that: +## Migration Checklist -> "Event-driven architecture eliminates cross-service WRITES (use events instead) but permits cross-service READS when necessary for denormalization and validation." +When converting a cross-service read to the event-driven pattern: -Remove the comment in lines 39-42 of [detection-strategies-service.js](app/services/detection-strategies-service.js#L39-L42) - this implementation is correct as-is. +1. Define a new event constant in `app/lib/event-constants.js` +2. Register an event handler in the owning service's `initializeEventListeners()` +3. Have the handler return the requested data +4. Replace the direct import/read with `EventBus.emit()` and use the returned value +5. Add a fallback for when no handler is registered (defensive coding) diff --git a/docs/developer/event-bus-architecture.md b/docs/developer/event-bus-architecture.md index 4a122ae8..5ce78bcf 100644 --- a/docs/developer/event-bus-architecture.md +++ b/docs/developer/event-bus-architecture.md @@ -57,34 +57,24 @@ ServiceB (modifies its own documents in response) | Operation | Allowed? | Pattern | |-----------|----------|---------| | Cross-service WRITES | ❌ NO | Use events instead | -| Cross-service READS | ✅ YES | Direct repository access permitted for denormalization and validation | +| Cross-service READS | ❌ NO | Use events instead (handlers can return data) | -**WRITES - Use Events:** -- ❌ Service A MUST NOT directly modify Service B's documents -- ✅ Service A emits event → Service B modifies its own documents -- Rationale: Maintains ownership boundaries, enables loose coupling - -**READS - Direct Repository Access:** -- ✅ Service A MAY read from Service B's repository -- Use cases: Building denormalized metadata (embedded_relationships), validation -- Must handle missing documents gracefully (null values, try/catch) -- Rationale: Reads are safe, idempotent, and necessary for materialized views +All cross-service communication — reads and writes — MUST go through the EventBus. +See [cross-service-reads-pattern.md](cross-service-reads-pattern.md) for details on how +`EventBus.emit()` returns fulfilled handler values, enabling request/response over events. **Example:** ```javascript -// DetectionStrategiesService -async beforeCreate(data) { - // ✅ ALLOWED: Read from analytics repository to build denormalized metadata - const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); - data.workspace.embedded_relationships.push({ - stix_id: analyticId, - name: analytic.stix.name, // Denormalized data from read - }); -} - +// BaseService requests validation bypass filtering via event +const results = await EventBus.emit(Events.VALIDATION_BYPASS_CHECK_REQUESTED, { + errors: allErrors, + stixType, +}); +const errors = results?.[0] ?? allErrors; + +// DetectionStrategiesService emits event so AnalyticsService updates its own documents async afterCreate(document) { - // ✅ REQUIRED: Emit event so AnalyticsService can update its own documents - await EventBus.emit('analytics-referenced', { ... }); + await EventBus.emit('x-mitre-detection-strategy::analytics-referenced', { ... }); } ``` @@ -402,10 +392,11 @@ Our EventBus extends Node.js's native `EventEmitter` with additional features: 1. **Built on Node.js Standard Library** - Leverages `events.EventEmitter` for reliability 2. **Async/Await Support** - Overrides `emit()` to handle async listeners with `Promise.allSettled` -3. **Logging & Debugging** - Logs all event registrations and emissions -4. **Event Audit Trail** - Maintains circular buffer of recent events for debugging -5. **Singleton Pattern** - Single shared bus instance across the application -6. **Increased Max Listeners** - Set to 50 to accommodate multiple services subscribing to common events +3. **Handler Return Values** - `emit()` collects and returns fulfilled handler values, enabling request/response patterns over the bus +4. **Logging & Debugging** - Logs all event registrations and emissions +5. **Event Audit Trail** - Maintains circular buffer of recent events for debugging +6. **Singleton Pattern** - Single shared bus instance across the application +7. **Increased Max Listeners** - Set to 50 to accommodate multiple services subscribing to common events The implementation is minimal - we only add what's necessary beyond the standard EventEmitter. From 7c6ab0c443c019a52043e616863f964713b33ce1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:13:32 -0400 Subject: [PATCH 266/370] feat: version system config, validate org identity, and propagate identity changes to objects - Refactor system-configuration-controller to use `next` for error handling; defer to global error handler instead of handling service layer exceptions manually in the controller. - Add new `app/lib/bypass-rule-constants.js` module to define constants for validation bypass rule reasons and trigger events. - Update `app/lib/database-configuration.js` to load the `ValidationBypassesService` before calling `checkSystemConfiguration()`. This is critical to ensure that the event listeners for `SYSTEM_CONFIGURATION_IDENTITY_CHANGED` are registered before we attempt to emit that event during the creation of the placeholder organization identity. This way, when the placeholder identity is created and the event is emitted, the listeners will be in place to create the necessary validation bypass rules for `x_mitre_modified_by_ref`, which will allow the tests to pass successfully. - Add new event type, `SYSTEM_CONFIGURATION_IDENTITY_CHANGED`, to `app/lib/event-constants.js`. - Add new field, `created_at`, to `systemConfigurationSchema` in `app/models/system-configuration.js` to track when each system configuration document is created. This will allow us to maintain a clear history of changes to the system configuration over time. - Add new field, `autoCreatedReason`, to the validation bypass rule model in `app/models/validation-bypass-rule.js` to track the reason why a validation bypass rule was automatically created. This will allow us to easily identify and manage validation bypass rules that were created due to identity changes. - Add new repository method, `AttackObjectsRepository.retrieveAllLatestByOrgIdentityRefs()`, to retrieve all latest attack objects that have `created_by_ref` or `x_mitre_modified_by_ref` values matching any of the provided organization identity refs. This will allow us to easily find all attack objects that are associated with any of the organization identities in the history of organization identity changes. - Add new repository method, `SystemConfigurationRepository.retrieveAllDistinctIdentityRefs()`, to retrieve a list of all distinct organization identity refs that are currently referenced in any system configuration documents. This will allow us to easily track all organization identities that have been used in the system configuration over time. - Modify `SystemConfigurationRepository.retrieveOne` to retrieve the latest system configuration document based on the `created_at` timestamp, rather than just retrieving a single document without any sorting. This will ensure that we always get the most recent system configuration, which is important now that we are creating a new system configuration document each time there is a change to the organization identity or namespace prefix. - Add new repository method, `ValidationBypassesRepository.deleteByReason`, to delete validation bypass rules based on the reason they were created. This will allow us to easily clean up old validation bypass rules that were created due to previous identity changes when a new identity change occurs. - Add new event handler to `AttackObjectsService` to handle the `SYSTEM_CONFIGURATION_IDENTITY_CHANGED` event. This handler will retrieve all attack objects that are associated with any of the organization identities in the history of identity changes, and it will update their `created_by_ref` and `x_mitre_modified_by_ref` values to point to the new organization identity as appropriate based on the rules outlined in the implementation plan. This will ensure that all attack objects are properly updated to reflect the new organization identity whenever an identity change occurs. - Refactor `system-configuration-service.js` to create a new system configuration document each time there is a change to the organization identity or namespace prefix, rather than updating an existing document. This will allow us to maintain a clear history of all changes to the system configuration over time, and it will also allow us to easily query this history if needed. Each time there is a change, we will create a new system configuration document with the updated values and a `created_at` timestamp. This way, we can track the evolution of the system configuration over time and have a clear record of when changes occurred. - Add event hanlder to `validation-bypasses-service.js` to handle the `SYSTEM_CONFIGURATION_IDENTITY_CHANGED` event. This handler will create new validation bypass rules for `x_mitre_modified_by_ref` based on the new organization identity, and it will also clean up old validation bypass rules that were created due to previous identity changes. This will ensure that the necessary validation bypass rules are always in place whenever an identity change occurs, which will allow the tests to pass successfully and will also ensure that the system continues to function properly after identity changes. - Add startup migration script to backfill existing system configuration documents with the new `created_at` field. This script will query all existing system configuration documents and add a `created_at` field with the current timestamp to each document. This way, we can ensure that all system configuration documents have a `created_at` field, which will allow us to maintain a clear history of changes over time. This script can be run as part of the existing database migration workflow that is executed at startup. --- .../system-configuration-controller.js | 47 +++---- app/lib/bypass-rule-constants.js | 11 ++ app/lib/database-configuration.js | 5 + app/lib/event-constants.js | 1 + app/models/system-configuration-model.js | 1 + app/models/validation-bypass-rule-model.js | 8 ++ app/repository/attack-objects-repository.js | 27 ++++ .../system-configurations-repository.js | 22 +++- .../validation-bypasses-repository.js | 8 ++ app/services/stix/attack-objects-service.js | 83 +++++++++++++ .../system/system-configuration-service.js | 116 ++++++++++++++---- .../system/validation-bypasses-service.js | 78 +++++++++++- ...20952-backfill-system-config-created-at.js | 26 ++++ 13 files changed, 373 insertions(+), 60 deletions(-) create mode 100644 app/lib/bypass-rule-constants.js create mode 100644 migrations/20260406220952-backfill-system-config-created-at.js diff --git a/app/controllers/system-configuration-controller.js b/app/controllers/system-configuration-controller.js index 9a3ce7c2..2b171dcd 100644 --- a/app/controllers/system-configuration-controller.js +++ b/app/controllers/system-configuration-controller.js @@ -4,7 +4,7 @@ const systemConfigurationService = require('../services/system/system-configurat const { SystemConfigurationService } = require('../services/system/system-configuration-service'); const logger = require('../lib/logger'); -exports.retrieveSystemVersion = function (req, res) { +exports.retrieveSystemVersion = function (req, res, next) { try { const systemVersionInfo = SystemConfigurationService.retrieveSystemVersion(); logger.debug( @@ -12,34 +12,31 @@ exports.retrieveSystemVersion = function (req, res) { ); return res.status(200).send(systemVersionInfo); } catch (err) { - logger.error('Unable to retrieve system version, failed with error: ' + err); - return res.status(500).send('Unable to retrieve system version. Server error.'); + return next(err); } }; -exports.retrieveAllowedValues = async function (req, res) { +exports.retrieveAllowedValues = async function (req, res, next) { try { const allowedValues = await systemConfigurationService.retrieveAllowedValues(); logger.debug('Success: Retrieved allowed values.'); return res.status(200).send(allowedValues); } catch (err) { - logger.error('Unable to retrieve allowed values, failed with error: ' + err); - return res.status(500).send('Unable to retrieve allowed values. Server error.'); + return next(err); } }; -exports.retrieveOrganizationIdentity = async function (req, res) { +exports.retrieveOrganizationIdentity = async function (req, res, next) { try { const identity = await systemConfigurationService.retrieveOrganizationIdentity(); logger.debug('Success: Retrieved organization identity.'); return res.status(200).send(identity); } catch (err) { - logger.error('Unable to retrieve organization identity, failed with error: ' + err); - return res.status(500).send('Unable to retrieve organization identity. Server error.'); + return next(err); } }; -exports.setOrganizationIdentity = async function (req, res) { +exports.setOrganizationIdentity = async function (req, res, next) { const organizationIdentity = req.body; if (!organizationIdentity.id) { logger.warn('Missing organization identity id'); @@ -51,23 +48,21 @@ exports.setOrganizationIdentity = async function (req, res) { logger.debug(`Success: Set organization identity to: ${organizationIdentity.id}`); return res.status(204).send(); } catch (err) { - logger.error('Unable to set organization identity, failed with error: ' + err); - return res.status(500).send('Unable to set organization identity. Server error.'); + return next(err); } }; -exports.retrieveAuthenticationConfig = function (req, res) { +exports.retrieveAuthenticationConfig = function (req, res, next) { try { const authenticationConfig = SystemConfigurationService.retrieveAuthenticationConfig(); logger.debug('Success: Retrieved authentication configuration.'); return res.status(200).send(authenticationConfig); } catch (err) { - logger.error('Unable to retrieve authentication configuration, failed with error: ' + err); - return res.status(500).send('Unable to retrieve authentication configuration. Server error.'); + return next(err); } }; -exports.retrieveDefaultMarkingDefinitions = async function (req, res) { +exports.retrieveDefaultMarkingDefinitions = async function (req, res, next) { try { const options = { refOnly: req.query.refOnly }; const defaultMarkingDefinitions = @@ -75,14 +70,11 @@ exports.retrieveDefaultMarkingDefinitions = async function (req, res) { logger.debug('Success: Retrieved default marking definitions.'); return res.status(200).send(defaultMarkingDefinitions); } catch (err) { - logger.error( - `Unable to retrieve default marking definitions, failed with error: ${err} (${err.markingDefinitionRef})`, - ); - return res.status(500).send('Unable to retrieve default marking definitions. Server error.'); + return next(err); } }; -exports.setDefaultMarkingDefinitions = async function (req, res) { +exports.setDefaultMarkingDefinitions = async function (req, res, next) { const defaultMarkingDefinitionIds = req.body; if (!defaultMarkingDefinitionIds) { logger.warn('Missing default marking definition ids'); @@ -97,23 +89,21 @@ exports.setDefaultMarkingDefinitions = async function (req, res) { logger.debug(`Success: Set default marking definitions`); return res.status(204).send(); } catch (err) { - logger.error('Unable to set default marking definitions, failed with error: ' + err); - return res.status(500).send('Unable to default marking definitions. Server error.'); + return next(err); } }; -exports.retrieveOrganizationNamespace = async function (req, res) { +exports.retrieveOrganizationNamespace = async function (req, res, next) { try { const namespace = await systemConfigurationService.retrieveOrganizationNamespace(); logger.debug('Success: Retrieved organization namespace.'); return res.status(200).send(namespace); } catch (err) { - logger.error('Unable to retrieve organization namespace, failed with error: ' + err); - return res.status(500).send('Unable to retrieve organization namespace. Server error.'); + return next(err); } }; -exports.setOrganizationNamespace = async function (req, res) { +exports.setOrganizationNamespace = async function (req, res, next) { const organizationNamespace = req.body; try { @@ -121,7 +111,6 @@ exports.setOrganizationNamespace = async function (req, res) { logger.debug(`Success: Set organization namespace to: ${organizationNamespace.prefix}`); return res.status(204).send(); } catch (err) { - logger.error('Unable to set organization namespace, failed with error: ' + err); - return res.status(500).send('Unable to set organization namespace. Server error.'); + return next(err); } }; diff --git a/app/lib/bypass-rule-constants.js b/app/lib/bypass-rule-constants.js new file mode 100644 index 00000000..788f87a6 --- /dev/null +++ b/app/lib/bypass-rule-constants.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * Central enumeration of auto-created bypass rule reasons. + * Used to distinguish bypass rules created by different system events, + * enabling targeted cleanup without affecting rules from other triggers. + */ +module.exports = Object.freeze({ + NAMESPACE: 'namespace', + IDENTITY: 'identity', +}); diff --git a/app/lib/database-configuration.js b/app/lib/database-configuration.js index 22aca2e0..9eb8aa35 100644 --- a/app/lib/database-configuration.js +++ b/app/lib/database-configuration.js @@ -18,6 +18,11 @@ const { AnonymousUserAccountNotSetError, } = require('../exceptions'); +// Ensure event listeners are registered before checkSystemConfiguration() emits events. +// ValidationBypassesService registers its listeners at module load time, so requiring it +// here guarantees they are in place before the SYSTEM_CONFIGURATION_IDENTITY_CHANGED event fires. +require('../services/system/validation-bypasses-service'); + async function createPlaceholderOrganizationIdentity() { // Create placeholder identity object const timestamp = new Date().toISOString(); diff --git a/app/lib/event-constants.js b/app/lib/event-constants.js index 73346de3..57afcd72 100644 --- a/app/lib/event-constants.js +++ b/app/lib/event-constants.js @@ -154,6 +154,7 @@ module.exports = Object.freeze({ // System Configuration SYSTEM_CONFIGURATION_NAMESPACE_CHANGED: 'system-configuration::namespace-changed', + SYSTEM_CONFIGURATION_IDENTITY_CHANGED: 'system-configuration::identity-changed', // Validation VALIDATION_BYPASS_CHECK_REQUESTED: 'validation-bypass::check-requested', diff --git a/app/models/system-configuration-model.js b/app/models/system-configuration-model.js index 7092518a..2eb5001f 100644 --- a/app/models/system-configuration-model.js +++ b/app/models/system-configuration-model.js @@ -11,6 +11,7 @@ const systemConfigurationDefinition = { range_start: { type: Number, default: null }, prefix: { type: String, default: null }, }, + created_at: { type: Date, default: Date.now }, }; // Create the schema diff --git a/app/models/validation-bypass-rule-model.js b/app/models/validation-bypass-rule-model.js index 3509a92b..7750f935 100644 --- a/app/models/validation-bypass-rule-model.js +++ b/app/models/validation-bypass-rule-model.js @@ -2,12 +2,20 @@ const mongoose = require('mongoose'); +const BypassRuleReasons = require('../lib/bypass-rule-constants'); + const validationBypassRuleDefinition = { fieldPath: { type: [String], required: true }, errorCode: { type: String, required: true }, stixType: { type: String, required: true }, suppressError: { type: Boolean, default: true }, autoCreated: { type: Boolean, default: false }, + autoCreatedReason: { + type: String, + enum: [...Object.values(BypassRuleReasons), null], + default: null, + }, + triggerEvent: { type: String, default: null }, }; const validationBypassRuleSchema = new mongoose.Schema(validationBypassRuleDefinition, { diff --git a/app/repository/attack-objects-repository.js b/app/repository/attack-objects-repository.js index 81b342f3..c0e816ca 100644 --- a/app/repository/attack-objects-repository.js +++ b/app/repository/attack-objects-repository.js @@ -133,6 +133,33 @@ class AttackObjectsRepository extends BaseRepository { } } + /** + * Retrieve all latest versions of objects whose created_by_ref or x_mitre_modified_by_ref + * matches any of the provided identity refs. Used for organization identity propagation. + * @param {string[]} identityRefs - Array of identity STIX IDs (the provenance chain) + * @returns {Promise} Array of plain objects (latest version per stix.id) + */ + async retrieveAllLatestByOrgIdentityRefs(identityRefs) { + try { + const aggregation = [ + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { + $match: { + $or: [ + { 'stix.created_by_ref': { $in: identityRefs } }, + { 'stix.x_mitre_modified_by_ref': { $in: identityRefs } }, + ], + }, + }, + ]; + return await this.model.aggregate(aggregation).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + async findByIdAndDelete(documentId) { try { return await this.model.findByIdAndDelete(documentId).exec(); diff --git a/app/repository/system-configurations-repository.js b/app/repository/system-configurations-repository.js index ede51662..c5d84ba4 100644 --- a/app/repository/system-configurations-repository.js +++ b/app/repository/system-configurations-repository.js @@ -18,12 +18,30 @@ class SystemConfigurationsRepository { } } + /** + * Retrieve the latest (most recent) system configuration document. + * Sorts by created_at descending so the newest document is returned. + */ async retrieveOne(options) { options = options ?? {}; if (options.lean) { - return await this.model.findOne().lean(); + return await this.model.findOne().sort({ created_at: -1 }).lean(); } else { - return await this.model.findOne(); + return await this.model.findOne().sort({ created_at: -1 }); + } + } + + /** + * Retrieve all distinct organization_identity_ref values across all config documents. + * Used to determine the full provenance chain of organization identities. + * @returns {Promise} Array of distinct identity ref strings + */ + async retrieveAllDistinctIdentityRefs() { + try { + const refs = await this.model.distinct('organization_identity_ref').exec(); + return refs.filter((ref) => ref != null); + } catch (err) { + throw new DatabaseError(err); } } } diff --git a/app/repository/validation-bypasses-repository.js b/app/repository/validation-bypasses-repository.js index 714384e8..a2a4ab47 100644 --- a/app/repository/validation-bypasses-repository.js +++ b/app/repository/validation-bypasses-repository.js @@ -73,6 +73,14 @@ class ValidationBypassesRepository { } } + async deleteByReason(reason) { + try { + return await this.model.deleteMany({ autoCreated: true, autoCreatedReason: reason }).exec(); + } catch (err) { + throw new DatabaseError(err); + } + } + async findAll() { try { return await this.model.find({}).lean().exec(); diff --git a/app/services/stix/attack-objects-service.js b/app/services/stix/attack-objects-service.js index 31927452..57f0e90b 100644 --- a/app/services/stix/attack-objects-service.js +++ b/app/services/stix/attack-objects-service.js @@ -187,6 +187,86 @@ class AttackObjectsService extends BaseService { } } + /** + * Initialize event listeners for organization identity propagation. + */ + static initializeEventListeners() { + const EventBus = require('../../lib/event-bus'); + const Events = require('../../lib/event-constants'); + + EventBus.on( + Events.SYSTEM_CONFIGURATION_IDENTITY_CHANGED, + AttackObjectsService.handleOrganizationIdentityChanged, + ); + + logger.info('AttackObjectsService: Event listeners initialized'); + } + + /** + * Handle organization identity changes by creating new versions of affected objects. + * Objects are updated based on field-specific provenance: + * - created_by_ref is updated only if it matches an identity in the provenance chain + * - x_mitre_modified_by_ref is updated only if it matches an identity in the provenance chain + * @param {Object} payload + * @param {string} payload.previousIdentityRef + * @param {string} payload.newIdentityRef + * @param {string[]} payload.organizationIdentityHistory + */ + static async handleOrganizationIdentityChanged(payload) { + const { previousIdentityRef, newIdentityRef, organizationIdentityHistory } = payload; + + // Skip propagation on first-time setup (no previous identity to propagate from) + // or if required payload fields are missing. + if (!previousIdentityRef || !organizationIdentityHistory || !newIdentityRef) { + return; + } + + const objects = await attackObjectsRepository.retrieveAllLatestByOrgIdentityRefs( + organizationIdentityHistory, + ); + + logger.info( + `AttackObjectsService: Creating new versions for ${objects.length} object(s) due to organization identity change`, + { newIdentityRef }, + ); + + for (const obj of objects) { + try { + const createdByInHistory = organizationIdentityHistory.includes(obj.stix.created_by_ref); + const modifiedByInHistory = organizationIdentityHistory.includes( + obj.stix.x_mitre_modified_by_ref, + ); + + const newVersion = { + workspace: obj.workspace, + stix: { + ...obj.stix, + modified: new Date().toISOString(), + }, + }; + + if (createdByInHistory) { + newVersion.stix.created_by_ref = newIdentityRef; + } + if (modifiedByInHistory) { + newVersion.stix.x_mitre_modified_by_ref = newIdentityRef; + } + + await attackObjectsRepository.save(newVersion); + + logger.info( + `AttackObjectsService: Created new version of ${obj.stix.id} with updated identity refs`, + { + createdByUpdated: createdByInHistory, + modifiedByUpdated: modifiedByInHistory, + }, + ); + } catch (error) { + logger.error(`AttackObjectsService: Error creating new version of ${obj.stix?.id}:`, error); + } + } + } + async retrieveOneByVersionLean(stixId, stixModified) { try { return await this.repository.retrieveOneByVersionLean(stixId, stixModified); @@ -259,5 +339,8 @@ class AttackObjectsService extends BaseService { module.exports.AttackObjectsService = AttackObjectsService; +// Initialize event listeners for identity propagation +AttackObjectsService.initializeEventListeners(); + // Export an instance of the service module.exports = new AttackObjectsService(null, attackObjectsRepository); diff --git a/app/services/system/system-configuration-service.js b/app/services/system/system-configuration-service.js index 0d9f8d31..f52ebc7c 100644 --- a/app/services/system/system-configuration-service.js +++ b/app/services/system/system-configuration-service.js @@ -7,6 +7,8 @@ const userAccountsService = require('./user-accounts-service'); const identitiesService = require('../stix/identities-service'); const markingDefinitionsService = require('../stix/marking-definitions-service'); const { BaseService } = require('../meta-classes'); +const EventBus = require('../../lib/event-bus'); +const Events = require('../../lib/event-constants'); const { SystemConfigurationNotFound, OrganizationIdentityNotSetError, @@ -105,18 +107,52 @@ class SystemConfigurationService extends BaseService { /** * @public * CRUD Operation: Update - * Sets the organization identity + * Sets the organization identity. + * Validates that the identity exists, creates a new config document (preserving history), + * and emits an event to trigger downstream propagation. */ async setOrganizationIdentity(stixId) { - const systemConfig = await this.repository.retrieveOne(); + // Validate that the identity exists + const identities = await identitiesService.retrieveById(stixId, { versions: 'latest' }); + if (identities.length === 0) { + throw new OrganizationIdentityNotFoundError(stixId); + } + + const currentConfig = await this.repository.retrieveOne({ lean: true }); + + if (currentConfig) { + // No-op if already set to this identity + if (currentConfig.organization_identity_ref === stixId) return; + + const previousIdentityRef = currentConfig.organization_identity_ref; + + // Create a new config document with updated identity ref + await this._createNewConfigVersion(currentConfig, { + organization_identity_ref: stixId, + }); + + // Determine the full provenance chain + const organizationIdentityHistory = await this.repository.retrieveAllDistinctIdentityRefs(); - if (systemConfig) { - systemConfig.organization_identity_ref = stixId; - await this.repository.constructor.saveDocument(systemConfig); + // Emit event for downstream propagation + await EventBus.emit(Events.SYSTEM_CONFIGURATION_IDENTITY_CHANGED, { + previousIdentityRef, + newIdentityRef: stixId, + organizationIdentityHistory, + }); } else { - const systemConfigData = { organization_identity_ref: stixId }; - const newConfig = this.repository.createNewDocument(systemConfigData); + // First-time setup: create initial config document + const newConfig = this.repository.createNewDocument({ + organization_identity_ref: stixId, + }); await this.repository.constructor.saveDocument(newConfig); + + // Emit event so validation bypass rules are created at startup + await EventBus.emit(Events.SYSTEM_CONFIGURATION_IDENTITY_CHANGED, { + previousIdentityRef: null, + newIdentityRef: stixId, + organizationIdentityHistory: [stixId], + }); } } @@ -157,14 +193,16 @@ class SystemConfigurationService extends BaseService { * Sets the default marking definitions */ async setDefaultMarkingDefinitions(stixIds) { - const systemConfig = await this.repository.retrieveOne(); + const currentConfig = await this.repository.retrieveOne({ lean: true }); - if (systemConfig) { - systemConfig.default_marking_definitions = stixIds; - await this.repository.constructor.saveDocument(systemConfig); + if (currentConfig) { + await this._createNewConfigVersion(currentConfig, { + default_marking_definitions: stixIds, + }); } else { - const systemConfigData = { default_marking_definitions: stixIds }; - const newConfig = this.repository.createNewDocument(systemConfigData); + const newConfig = this.repository.createNewDocument({ + default_marking_definitions: stixIds, + }); await this.repository.constructor.saveDocument(newConfig); } } @@ -196,14 +234,15 @@ class SystemConfigurationService extends BaseService { * Internal method for user account management */ async setAnonymousUserAccountId(userAccountId) { - const systemConfig = await this.repository.retrieveOne(); + const currentConfig = await this.repository.retrieveOne({ lean: true }); - if (!systemConfig) { + if (!currentConfig) { throw new SystemConfigurationNotFound(); } - systemConfig.anonymous_user_account_id = userAccountId; - await this.repository.constructor.saveDocument(systemConfig); + await this._createNewConfigVersion(currentConfig, { + anonymous_user_account_id: userAccountId, + }); } /** @@ -238,23 +277,54 @@ class SystemConfigurationService extends BaseService { * Sets the organization namespace */ async setOrganizationNamespace(namespace) { - const systemConfig = await this.repository.retrieveOne(); + const currentConfig = await this.repository.retrieveOne({ lean: true }); - if (!systemConfig) { + if (!currentConfig) { throw new SystemConfigurationNotFound(); } - systemConfig.organization_namespace = namespace; - await this.repository.constructor.saveDocument(systemConfig); + await this._createNewConfigVersion(currentConfig, { + organization_namespace: namespace, + }); // Emit event so ValidationBypassesService can manage its own bypass rules - const EventBus = require('../../lib/event-bus'); - const Events = require('../../lib/event-constants'); await EventBus.emit(Events.SYSTEM_CONFIGURATION_NAMESPACE_CHANGED, { namespace, }); } + /** + * @public + * CRUD Operation: Read + * Returns all distinct organization identity refs from all config documents. + * This represents the full provenance chain of organization identities. + * @returns {Promise} + */ + async retrieveOrganizationIdentityHistory() { + return await this.repository.retrieveAllDistinctIdentityRefs(); + } + + /** + * @private + * Creates a new system configuration document by copying the latest config + * and applying the given field overrides. This preserves history by leaving + * the previous document intact. + * @param {Object} currentConfig - The current config document (lean) + * @param {Object} overrides - Fields to update in the new document + * @returns {Promise} The saved new config document + */ + async _createNewConfigVersion(currentConfig, overrides) { + const configData = { + organization_identity_ref: currentConfig.organization_identity_ref, + anonymous_user_account_id: currentConfig.anonymous_user_account_id, + default_marking_definitions: currentConfig.default_marking_definitions, + organization_namespace: currentConfig.organization_namespace, + ...overrides, + }; + const newConfig = this.repository.createNewDocument(configData); + return await this.repository.constructor.saveDocument(newConfig); + } + /** * Override of base class create() because: * 1. create() requires a STIX `type` -- this service does not define a type diff --git a/app/services/system/validation-bypasses-service.js b/app/services/system/validation-bypasses-service.js index 79766193..36fa3786 100644 --- a/app/services/system/validation-bypasses-service.js +++ b/app/services/system/validation-bypasses-service.js @@ -2,6 +2,9 @@ const { BaseService } = require('../meta-classes'); const validationBypassesRepository = require('../../repository/validation-bypasses-repository'); +const BypassRuleReasons = require('../../lib/bypass-rule-constants'); +const EventBus = require('../../lib/event-bus'); +const Events = require('../../lib/event-constants'); const logger = require('../../lib/logger'); class ValidationBypassesService { @@ -10,14 +13,16 @@ class ValidationBypassesService { } static initializeEventListeners() { - const EventBus = require('../../lib/event-bus'); - const Events = require('../../lib/event-constants'); - EventBus.on( Events.SYSTEM_CONFIGURATION_NAMESPACE_CHANGED, ValidationBypassesService.handleNamespaceChanged.bind(ValidationBypassesService), ); + EventBus.on( + Events.SYSTEM_CONFIGURATION_IDENTITY_CHANGED, + ValidationBypassesService.handleIdentityChanged.bind(ValidationBypassesService), + ); + EventBus.on( Events.VALIDATION_BYPASS_CHECK_REQUESTED, ValidationBypassesService.handleBypassCheckRequested.bind(ValidationBypassesService), @@ -116,17 +121,38 @@ class ValidationBypassesService { return false; } + /** + * Handle organization identity configuration changes. + * Creates bypass rules for x_mitre_modified_by_ref validation. + * @param {Object} payload - Event payload + */ + // eslint-disable-next-line no-unused-vars + static async handleIdentityChanged(payload) { + const service = module.exports; + + // Remove any previously auto-created identity bypass rules + await service.removeByReason(BypassRuleReasons.IDENTITY); + + // Create bypass rules for x_mitre_modified_by_ref across all STIX types + const { stixTypeToAttackIdMapping } = require('@mitre-attack/attack-data-model'); + const stixTypes = Object.keys(stixTypeToAttackIdMapping); + await service.createIdentityRules(stixTypes, Events.SYSTEM_CONFIGURATION_IDENTITY_CHANGED); + } + /** * Create bypass rules for namespace-prefixed ATT&CK IDs across all relevant STIX types. * @param {string[]} stixTypes - STIX types that support ATT&CK IDs */ async createNamespaceRules(stixTypes) { + const Events = require('../../lib/event-constants'); const rules = stixTypes.map((stixType) => ({ fieldPath: ['external_references', '0', 'external_id'], errorCode: 'custom', stixType, suppressError: true, autoCreated: true, + autoCreatedReason: BypassRuleReasons.NAMESPACE, + triggerEvent: Events.SYSTEM_CONFIGURATION_NAMESPACE_CHANGED, })); for (const rule of rules) { @@ -143,11 +169,51 @@ class ValidationBypassesService { } /** - * Remove all auto-created bypass rules (e.g., when namespace is cleared). + * Create bypass rules for x_mitre_modified_by_ref across all relevant STIX types. + * These bypass the ADM rule that requires x_mitre_modified_by_ref to be the MITRE identity. + * @param {string[]} stixTypes - STIX types that support ATT&CK IDs + * @param {string} triggerEvent - The event that triggered rule creation + */ + async createIdentityRules(stixTypes, triggerEvent) { + const rules = stixTypes.map((stixType) => ({ + fieldPath: ['x_mitre_modified_by_ref'], + errorCode: 'invalid_value', + stixType, + suppressError: true, + autoCreated: true, + autoCreatedReason: BypassRuleReasons.IDENTITY, + triggerEvent, + })); + + for (const rule of rules) { + try { + await this.repository.save(rule); + } catch (err) { + // Skip duplicates — rule may already exist + if (err.name === 'DuplicateIdError') continue; + throw err; + } + } + + logger.info(`Created ${rules.length} identity validation bypass rules`); + } + + /** + * Remove auto-created bypass rules by reason. + * @param {string} reason - The autoCreatedReason value to match + */ + async removeByReason(reason) { + const result = await this.repository.deleteByReason(reason); + logger.info( + `Removed ${result.deletedCount} auto-created validation bypass rules (reason: ${reason})`, + ); + } + + /** + * Remove all auto-created namespace bypass rules. */ async removeNamespaceRules() { - const result = await this.repository.deleteAutoCreated(); - logger.info(`Removed ${result.deletedCount} auto-created validation bypass rules`); + await this.removeByReason(BypassRuleReasons.NAMESPACE); } } diff --git a/migrations/20260406220952-backfill-system-config-created-at.js b/migrations/20260406220952-backfill-system-config-created-at.js new file mode 100644 index 00000000..1914ac75 --- /dev/null +++ b/migrations/20260406220952-backfill-system-config-created-at.js @@ -0,0 +1,26 @@ +'use strict'; + +/** + * Backfill the `created_at` field on existing system configuration documents. + * + * Previously, the system configuration collection contained a single document + * that was updated in-place. With the move to versioned config documents, each + * document needs a `created_at` timestamp to enable sorting by recency. + * + * This migration sets `created_at` to the current time on any document that + * does not already have it. + */ +module.exports = { + async up(db) { + const result = await db + .collection('systemconfigurations') + .updateMany({ created_at: { $exists: false } }, { $set: { created_at: new Date() } }); + console.log( + `Backfilled created_at on ${result.modifiedCount} system configuration document(s)`, + ); + }, + + async down(db) { + await db.collection('systemconfigurations').updateMany({}, { $unset: { created_at: '' } }); + }, +}; From c54af948270d8de8fdd919957006f84a403a2ae7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:50:11 -0400 Subject: [PATCH 267/370] feat: add support to load ADM validation bypass rules at runtime via JSON config - Additionally sets a new bypass-rule-creation type of 'static' to clarify how the rule came into being - The server runs checkForStaticBypassRules at startup to load bypass rules from JSON config - Adds WB_REST_STATIC_BYPASS_RULES_PATH and config.configurationFiles.staticBypassRules.Path to let teams set location to custom JSON file - Provide a default set of bypass rules that should cover most use cases given the current Workbench behaviors - Add new bypass warning to enable transforming errors into warnings (fail open mechanism) --- app/config/config.js | 5 ++ app/lib/bypass-rule-constants.js | 1 + app/lib/database-configuration.js | 6 ++ app/lib/default-bypass-rules.json | 38 ++++++++ app/models/validation-bypass-rule-model.js | 1 + app/services/meta-classes/base.service.js | 6 +- .../system/validation-bypasses-service.js | 86 ++++++++++++++++--- 7 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 app/lib/default-bypass-rules.json diff --git a/app/config/config.js b/app/config/config.js index a4b20726..5297239c 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -238,6 +238,11 @@ function loadConfig() { default: './app/lib/default-static-marking-definitions/', env: 'WB_REST_STATIC_MARKING_DEFS_PATH', }, + staticBypassRulesPath: { + doc: 'Location of a JSON file containing default validation bypass rules to load at startup', + default: './app/lib/default-bypass-rules.json', + env: 'WB_REST_STATIC_BYPASS_RULES_PATH', + }, }, scheduler: { syncCollectionIndexesCron: { diff --git a/app/lib/bypass-rule-constants.js b/app/lib/bypass-rule-constants.js index 788f87a6..7ae637fc 100644 --- a/app/lib/bypass-rule-constants.js +++ b/app/lib/bypass-rule-constants.js @@ -8,4 +8,5 @@ module.exports = Object.freeze({ NAMESPACE: 'namespace', IDENTITY: 'identity', + STATIC: 'static', }); diff --git a/app/lib/database-configuration.js b/app/lib/database-configuration.js index 9eb8aa35..8b7a0191 100644 --- a/app/lib/database-configuration.js +++ b/app/lib/database-configuration.js @@ -262,10 +262,16 @@ async function checkForStaticMarkingDefinitions() { } } +async function checkForStaticBypassRules() { + const validationBypassesService = require('../services/system/validation-bypasses-service'); + await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); +} + exports.checkSystemConfiguration = async function () { logger.info(`Performing system configuration check...`); await checkForOrganizationIdentity(); await checkForAnonymousUserAccount(); await checkForInvalidEnterpriseCollectionId(); await checkForStaticMarkingDefinitions(); + await checkForStaticBypassRules(); }; diff --git a/app/lib/default-bypass-rules.json b/app/lib/default-bypass-rules.json new file mode 100644 index 00000000..52227b7d --- /dev/null +++ b/app/lib/default-bypass-rules.json @@ -0,0 +1,38 @@ +[ + { + "fieldPath": ["x_mitre_shortname"], + "errorCode": "invalid_value", + "stixType": "x-mitre-tactic", + "suppressError": false, + "warningMessage": "Tactic shortname does not match predefined ATT&CK tactics. This may prevent compatibility with official ATT&CK data but can be used for custom taxonomies.", + "_comment": "Warn about non-standard tactic shortnames instead of blocking" + }, + { + "fieldPath": ["x_mitre_domains"], + "errorCode": "invalid_type", + "stixType": "intrusion-set", + "suppressError": true, + "_comment": "Server sets x_mitre_domains for intrusion-set (assigned during bundle export)" + }, + { + "fieldPath": ["x_mitre_domains"], + "errorCode": "invalid_type", + "stixType": "campaign", + "suppressError": true, + "_comment": "Server sets x_mitre_domains for campaign (assigned during bundle export)" + }, + { + "fieldPath": ["x_mitre_domains"], + "errorCode": "invalid_type", + "stixType": "x-mitre-matrix", + "suppressError": true, + "_comment": "Server sets x_mitre_domains for x-mitre-matrix (assigned during bundle export)" + }, + { + "fieldPath": ["x_mitre_domains"], + "errorCode": "invalid_type", + "stixType": "x-mitre-detection-strategy", + "suppressError": true, + "_comment": "Server sets x_mitre_domains for x-mitre-detection-strategy (assigned during bundle export)" + } +] diff --git a/app/models/validation-bypass-rule-model.js b/app/models/validation-bypass-rule-model.js index 7750f935..06d4c1cc 100644 --- a/app/models/validation-bypass-rule-model.js +++ b/app/models/validation-bypass-rule-model.js @@ -16,6 +16,7 @@ const validationBypassRuleDefinition = { default: null, }, triggerEvent: { type: String, default: null }, + warningMessage: { type: String, default: null }, }; const validationBypassRuleSchema = new mongoose.Schema(validationBypassRuleDefinition, { diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index d9016fc9..f2f3cc31 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -432,10 +432,10 @@ class BaseService extends ServiceWithHooks { stixType, }); - // The handler returns the non-bypassed errors array - const errors = results?.[0] ?? allErrors; + // The handler returns { errors, warnings } + const bypassResult = results?.[0] ?? { errors: allErrors, warnings: [] }; - return { errors, warnings: [] }; + return { errors: bypassResult.errors, warnings: bypassResult.warnings }; } /** diff --git a/app/services/system/validation-bypasses-service.js b/app/services/system/validation-bypasses-service.js index 36fa3786..dbf34132 100644 --- a/app/services/system/validation-bypasses-service.js +++ b/app/services/system/validation-bypasses-service.js @@ -1,5 +1,7 @@ 'use strict'; +const fs = require('fs').promises; + const { BaseService } = require('../meta-classes'); const validationBypassesRepository = require('../../repository/validation-bypasses-repository'); const BypassRuleReasons = require('../../lib/bypass-rule-constants'); @@ -54,24 +56,29 @@ class ValidationBypassesService { /** * Handle a validation bypass check request from the event bus. - * Returns an array of non-bypassed errors. + * Returns non-bypassed errors and any warnings generated by bypass rules. * @param {Object} payload * @param {Array} payload.errors - Validation errors to check * @param {string} payload.stixType - The STIX type being validated - * @returns {Promise} Non-bypassed errors + * @returns {Promise<{ errors: Array, warnings: Array }>} */ static async handleBypassCheckRequested(payload) { const { errors, stixType } = payload; const service = module.exports; const nonBypassed = []; + const warnings = []; for (const error of errors) { - const bypassed = await service.isErrorBypassed(error, stixType); - if (!bypassed) { + const result = await service.checkBypassRule(error, stixType); + if (result.bypassed) { + if (result.warningMessage) { + warnings.push({ message: result.warningMessage, path: error.path, code: error.code }); + } + } else { nonBypassed.push(error); } } - return nonBypassed; + return { errors: nonBypassed, warnings }; } async retrieveAll(options) { @@ -92,18 +99,21 @@ class ValidationBypassesService { } /** - * Check if a validation error should be bypassed based on stored rules. + * Check if a validation error matches a bypass rule. + * Returns whether the error is bypassed and any warning message to emit. + * A rule matches if it either suppresses the error or converts it to a warning. * @param {Object} error - The validation error ({ path, code, ... }) * @param {string} stixType - The STIX type being validated - * @returns {Promise} True if the error should be bypassed + * @returns {Promise<{ bypassed: boolean, warningMessage: string|null }>} */ - async isErrorBypassed(error, stixType) { + async checkBypassRule(error, stixType) { const rules = await this.repository.findAll(); const errorPathStr = JSON.stringify(error.path.map(String)); for (const rule of rules) { - if (!rule.suppressError) continue; + // A rule is actionable if it suppresses the error or converts it to a warning + if (!rule.suppressError && !rule.warningMessage) continue; // Check stixType match ('all' matches any type) if (rule.stixType !== 'all' && rule.stixType !== stixType) continue; @@ -115,10 +125,10 @@ class ValidationBypassesService { const rulePathStr = JSON.stringify(rule.fieldPath.map(String)); if (rulePathStr !== errorPathStr) continue; - return true; + return { bypassed: true, warningMessage: rule.warningMessage || null }; } - return false; + return { bypassed: false, warningMessage: null }; } /** @@ -215,6 +225,60 @@ class ValidationBypassesService { async removeNamespaceRules() { await this.removeByReason(BypassRuleReasons.NAMESPACE); } + + /** + * Load static bypass rules from a JSON file. + * Rules are idempotent — duplicates (by fieldPath + errorCode + stixType) are skipped. + * @param {string} filePath - Path to the JSON file containing bypass rules + */ + async loadStaticRules(filePath) { + if (!filePath) { + logger.info('No path provided for static bypass rules.'); + return; + } + + let fileData; + try { + fileData = await fs.readFile(filePath, 'utf8'); + } catch (err) { + if (err.code === 'ENOENT') { + logger.info(`Static bypass rules file not found at ${filePath}, skipping.`); + return; + } + throw err; + } + + const rules = JSON.parse(fileData); + + let created = 0; + let skipped = 0; + for (const rule of rules) { + // Strip _comment fields (documentation only, not stored) + // eslint-disable-next-line no-unused-vars + const { _comment, ...ruleData } = rule; + + const bypassRule = { + ...ruleData, + autoCreated: true, + autoCreatedReason: BypassRuleReasons.STATIC, + }; + + try { + await this.repository.save(bypassRule); + created++; + } catch (err) { + if (err.name === 'DuplicateIdError') { + skipped++; + continue; + } + throw err; + } + } + + logger.info( + `Static bypass rules: ${created} created, ${skipped} already existed (from ${filePath})`, + ); + } } const service = new ValidationBypassesService(validationBypassesRepository); From 0918417dec84a185af41ccf014aa80b7ca471845 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:35:07 -0400 Subject: [PATCH 268/370] feat(models): array-based stix fields now default to undefined instead of empty list Includes a startup script to migrate existing databases to comply with the Mongoose model updates. --- app/models/campaign-model.js | 4 +- app/models/data-source-model.js | 6 +- app/models/detection-strategy-model.js | 4 +- app/models/group-model.js | 4 +- app/models/identity-model.js | 4 +- app/models/matrix-model.js | 2 +- app/models/software-model.js | 6 +- app/models/subschemas/attack-pattern.js | 4 +- app/models/subschemas/stix-core.js | 4 +- app/models/tactic-model.js | 2 +- ...0260407180930-remove-empty-array-fields.js | 114 ++++++++++++++++++ 11 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 migrations/20260407180930-remove-empty-array-fields.js diff --git a/app/models/campaign-model.js b/app/models/campaign-model.js index 57155e2d..e4212799 100644 --- a/app/models/campaign-model.js +++ b/app/models/campaign-model.js @@ -10,7 +10,7 @@ const stixCampaign = { modified: { type: Date, required: true }, name: { type: String, required: true }, description: String, - aliases: [String], + aliases: { type: [String], default: undefined }, first_seen: { type: Date, required: true }, last_seen: { type: Date, required: true }, @@ -21,7 +21,7 @@ const stixCampaign = { x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_version: String, x_mitre_attack_spec_version: String, - x_mitre_contributors: [String], + x_mitre_contributors: { type: [String], default: undefined }, }; // Create the definition diff --git a/app/models/data-source-model.js b/app/models/data-source-model.js index f3caea59..8c2e0916 100644 --- a/app/models/data-source-model.js +++ b/app/models/data-source-model.js @@ -13,13 +13,13 @@ const stixDataSource = { // ATT&CK custom stix properties x_mitre_modified_by_ref: String, - x_mitre_platforms: [String], + x_mitre_platforms: { type: [String], default: undefined }, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_domains: { type: [String], default: undefined }, x_mitre_version: String, x_mitre_attack_spec_version: String, - x_mitre_contributors: [String], - x_mitre_collection_layers: [String], + x_mitre_contributors: { type: [String], default: undefined }, + x_mitre_collection_layers: { type: [String], default: undefined }, }; // Create the definition diff --git a/app/models/detection-strategy-model.js b/app/models/detection-strategy-model.js index cffa0aa6..7493fd3c 100644 --- a/app/models/detection-strategy-model.js +++ b/app/models/detection-strategy-model.js @@ -13,8 +13,8 @@ const stixDetectionStrategy = { x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_version: String, x_mitre_attack_spec_version: String, - x_mitre_domains: [String], - x_mitre_analytic_refs: [String], + x_mitre_domains: { type: [String], default: undefined }, + x_mitre_analytic_refs: { type: [String], default: undefined }, x_mitre_contributors: { type: [String], default: undefined }, }; diff --git a/app/models/group-model.js b/app/models/group-model.js index 20dfd86a..2846bfac 100644 --- a/app/models/group-model.js +++ b/app/models/group-model.js @@ -12,13 +12,13 @@ const stixIntrusionSet = { description: String, // ATT&CK custom stix properties - aliases: [String], + aliases: { type: [String], default: undefined }, x_mitre_modified_by_ref: String, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_domains: { type: [String], default: undefined }, // TBD drop this property x_mitre_version: String, x_mitre_attack_spec_version: String, - x_mitre_contributors: [String], + x_mitre_contributors: { type: [String], default: undefined }, }; // Create the definition diff --git a/app/models/identity-model.js b/app/models/identity-model.js index 81b6e281..e45a2f6f 100644 --- a/app/models/identity-model.js +++ b/app/models/identity-model.js @@ -10,9 +10,9 @@ const identityProperties = { modified: { type: Date, required: true }, name: { type: String, required: true }, description: String, - roles: [String], + roles: { type: [String], default: undefined }, identity_class: String, - sectors: [String], + sectors: { type: [String], default: undefined }, contact_information: String, // ATT&CK custom stix properties diff --git a/app/models/matrix-model.js b/app/models/matrix-model.js index a84f248b..4cdbd496 100644 --- a/app/models/matrix-model.js +++ b/app/models/matrix-model.js @@ -12,7 +12,7 @@ const matrixProperties = { description: String, // ATT&CK custom stix properties - tactic_refs: [String], + tactic_refs: { type: [String], default: undefined }, x_mitre_modified_by_ref: String, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_domains: { type: [String], default: undefined }, // TBD drop this property diff --git a/app/models/software-model.js b/app/models/software-model.js index 0c018f68..01207b03 100644 --- a/app/models/software-model.js +++ b/app/models/software-model.js @@ -14,13 +14,13 @@ const stixMalware = { // ATT&CK custom stix properties x_mitre_modified_by_ref: String, - x_mitre_platforms: [String], + x_mitre_platforms: { type: [String], default: undefined }, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_domains: { type: [String], default: undefined }, x_mitre_version: String, x_mitre_attack_spec_version: String, - x_mitre_contributors: [String], - x_mitre_aliases: [String], + x_mitre_contributors: { type: [String], default: undefined }, + x_mitre_aliases: { type: [String], default: undefined }, }; // Create the definition diff --git a/app/models/subschemas/attack-pattern.js b/app/models/subschemas/attack-pattern.js index 169a74fa..4b0052ed 100644 --- a/app/models/subschemas/attack-pattern.js +++ b/app/models/subschemas/attack-pattern.js @@ -14,13 +14,13 @@ module.exports.attackPattern = { // ATT&CK custom STIX properties x_mitre_attack_spec_version: String, - x_mitre_contributors: [String], + x_mitre_contributors: { type: [String], default: undefined }, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_detection: String, x_mitre_domains: { type: [String], default: undefined }, x_mitre_is_subtechnique: { type: Boolean, required: true, default: false }, x_mitre_modified_by_ref: String, - x_mitre_platforms: [String], + x_mitre_platforms: { type: [String], default: undefined }, x_mitre_version: String, }; diff --git a/app/models/subschemas/stix-core.js b/app/models/subschemas/stix-core.js index e66a947c..849ead53 100644 --- a/app/models/subschemas/stix-core.js +++ b/app/models/subschemas/stix-core.js @@ -48,6 +48,6 @@ module.exports.commonRequiredSDO = { module.exports.commonOptionalSDO = { created_by_ref: { type: String }, revoked: { type: Boolean }, - external_references: [externalReferenceSchema], - object_marking_refs: [String], + external_references: { type: [externalReferenceSchema], default: undefined }, + object_marking_refs: { type: [String], default: undefined }, }; diff --git a/app/models/tactic-model.js b/app/models/tactic-model.js index 24b5f803..38b995ff 100644 --- a/app/models/tactic-model.js +++ b/app/models/tactic-model.js @@ -17,7 +17,7 @@ const stixTactic = { x_mitre_domains: { type: [String], default: undefined }, x_mitre_version: String, x_mitre_attack_spec_version: String, - x_mitre_contributors: [String], + x_mitre_contributors: { type: [String], default: undefined }, x_mitre_shortname: String, }; diff --git a/migrations/20260407180930-remove-empty-array-fields.js b/migrations/20260407180930-remove-empty-array-fields.js new file mode 100644 index 00000000..b3fcf558 --- /dev/null +++ b/migrations/20260407180930-remove-empty-array-fields.js @@ -0,0 +1,114 @@ +'use strict'; + +/** + * Remove empty array values from STIX fields that previously defaulted to []. + * + * Mongoose's default behavior for `[String]` schema fields is to initialize + * them as empty arrays. The STIX 2.1 specification states that list properties + * MUST NOT be empty — they should be absent rather than present as `[]`. + * + * The corresponding model schemas have been updated to `{ type: [String], default: undefined }` + * so new documents will omit these fields when not populated. This migration + * cleans up existing documents that already have empty arrays stored. + */ + +// Fields from stix-core.js (commonOptionalSDO) — shared across all STIX types. +const coreFields = ['stix.external_references', 'stix.object_marking_refs']; + +// Type-specific fields grouped by the STIX types they apply to. +// Every field lives in the `attackObjects` collection (discriminator pattern). +const fieldsByType = { + campaign: ['stix.aliases', 'stix.x_mitre_contributors'], + 'data-source': [ + 'stix.x_mitre_platforms', + 'stix.x_mitre_contributors', + 'stix.x_mitre_collection_layers', + ], + 'x-mitre-detection-strategy': ['stix.x_mitre_domains', 'stix.x_mitre_analytic_refs'], + 'intrusion-set': ['stix.aliases', 'stix.x_mitre_contributors'], + identity: ['stix.roles', 'stix.sectors'], + 'x-mitre-matrix': ['stix.tactic_refs'], + malware: ['stix.x_mitre_platforms', 'stix.x_mitre_contributors', 'stix.x_mitre_aliases'], + tool: ['stix.x_mitre_platforms', 'stix.x_mitre_contributors', 'stix.x_mitre_aliases'], + 'x-mitre-tactic': ['stix.x_mitre_contributors'], +}; + +module.exports = { + async up(db) { + const collection = db.collection('attackObjects'); + let totalModified = 0; + + // 1. Core fields (apply to all STIX types) + const coreOrConditions = coreFields.map((field) => ({ [field]: { $eq: [] } })); + const coreUnset = {}; + for (const field of coreFields) { + coreUnset[field] = ''; + } + + const coreResult = await collection.updateMany( + { $or: coreOrConditions }, + { $unset: coreUnset }, + ); + if (coreResult.modifiedCount > 0) { + console.log( + `Removed empty arrays from ${coreResult.modifiedCount} document(s): ${coreFields.join(', ')}`, + ); + totalModified += coreResult.modifiedCount; + } + + // 2. Type-specific fields + for (const [stixType, fields] of Object.entries(fieldsByType)) { + const orConditions = fields.map((field) => ({ [field]: { $eq: [] } })); + const filter = { + 'stix.type': stixType, + $or: orConditions, + }; + + const unsetFields = {}; + for (const field of fields) { + unsetFields[field] = ''; + } + + const result = await collection.updateMany(filter, { $unset: unsetFields }); + if (result.modifiedCount > 0) { + console.log( + `Removed empty arrays from ${result.modifiedCount} ${stixType} document(s): ${fields.join(', ')}`, + ); + totalModified += result.modifiedCount; + } + } + + console.log(`Total documents updated: ${totalModified}`); + }, + + async down(db) { + // Restore empty arrays on documents that are missing these fields. + // This is a best-effort reverse — it cannot distinguish between fields that + // were never set vs fields that were unset by the up() migration. + const collection = db.collection('attackObjects'); + + // 1. Core fields + const coreSet = {}; + const coreOrConditions = []; + for (const field of coreFields) { + coreSet[field] = []; + coreOrConditions.push({ [field]: { $exists: false } }); + } + await collection.updateMany({ $or: coreOrConditions }, { $set: coreSet }); + + // 2. Type-specific fields + for (const [stixType, fields] of Object.entries(fieldsByType)) { + const setFields = {}; + const orConditions = []; + for (const field of fields) { + setFields[field] = []; + orConditions.push({ [field]: { $exists: false } }); + } + + await collection.updateMany( + { 'stix.type': stixType, $or: orConditions }, + { $set: setFields }, + ); + } + }, +}; From 58d05b6ae03e514ed9da1586b2c3b9ea1a8971d2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:39:57 -0400 Subject: [PATCH 269/370] fix: restores revoked key in create + updateFull pipelines - New objects get revoked=false - New versions varry forward existingObject.stix.revoked - Updates carry forward document.stix.revoked Resolves an issue where revoked gets dropped and cascades into ADM validation issues --- app/services/meta-classes/base.service.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index f2f3cc31..35505c26 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -558,16 +558,18 @@ class BaseService extends ServiceWithHooks { } if (existingObject) { - // New version of an existing object — only set modified_by + // New version of an existing object — carry forward revoked status, set modified_by + data.stix.revoked = existingObject.stix.revoked ?? false; data.stix.x_mitre_modified_by_ref = organizationIdentityRef; } else { - // Brand-new object — set ID, created_by, modified_by + // Brand-new object — set ID, created_by, modified_by, revoked if (!data.stix.id) { data.stix.id = `${data.stix.type}--${uuid.v4()}`; } if (!data.stix.created) { data.stix.created = new Date().toISOString(); } + data.stix.revoked = false; data.stix.created_by_ref = organizationIdentityRef; data.stix.x_mitre_modified_by_ref = organizationIdentityRef; } @@ -707,6 +709,7 @@ class BaseService extends ServiceWithHooks { // Compose server-controlled fields from existing document data.stix.x_mitre_attack_spec_version = document.stix.x_mitre_attack_spec_version; + data.stix.revoked = document.stix.revoked ?? false; // Preserve x_mitre_is_subtechnique — changing subtechnique status requires // the dedicated conversion endpoints, not the generic update path. From cf186ab11a54d44b2e139b9a7f70990109a19577 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:14:28 -0400 Subject: [PATCH 270/370] test: update marking definition test to include object marking refs Change is needed after model update to default undefined array fields to undefined rather than empty lists --- app/tests/api/marking-definitions/marking-definitions.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/tests/api/marking-definitions/marking-definitions.spec.js b/app/tests/api/marking-definitions/marking-definitions.spec.js index 7e3798fb..055fa8db 100644 --- a/app/tests/api/marking-definitions/marking-definitions.spec.js +++ b/app/tests/api/marking-definitions/marking-definitions.spec.js @@ -24,6 +24,7 @@ const initialObjectData = { definition_type: 'statement', definition: { statement: 'This is a marking definition.' }, created_by_ref: 'identity--6444f546-6900-4456-b3b1-015c88d70dab', + object_marking_refs: ['marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9'], }, }; From 82c8699bdd81939427225974c8a48147af855bc4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:15:18 -0400 Subject: [PATCH 271/370] fix: add x_mitre_contributors for attack-patterns to start script to remove empty arr fields --- migrations/20260407180930-remove-empty-array-fields.js | 1 + 1 file changed, 1 insertion(+) diff --git a/migrations/20260407180930-remove-empty-array-fields.js b/migrations/20260407180930-remove-empty-array-fields.js index b3fcf558..d2a6e1ab 100644 --- a/migrations/20260407180930-remove-empty-array-fields.js +++ b/migrations/20260407180930-remove-empty-array-fields.js @@ -18,6 +18,7 @@ const coreFields = ['stix.external_references', 'stix.object_marking_refs']; // Type-specific fields grouped by the STIX types they apply to. // Every field lives in the `attackObjects` collection (discriminator pattern). const fieldsByType = { + 'attack-pattern': ['stix.x_mitre_contributors'], campaign: ['stix.aliases', 'stix.x_mitre_contributors'], 'data-source': [ 'stix.x_mitre_platforms', From 9691f93e137388766dd95a69ec7df50d784c90a5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:39:31 -0400 Subject: [PATCH 272/370] fix: add static adm bypass rules for x_mitre_modified_by_ref This is a temporary holdover to resolve issue where existing WB deployments do not trigger dynamic bypass rule hydration due to the way that the organization identity is managed on the frontend. --- app/lib/default-bypass-rules.json | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/app/lib/default-bypass-rules.json b/app/lib/default-bypass-rules.json index 52227b7d..685be3c0 100644 --- a/app/lib/default-bypass-rules.json +++ b/app/lib/default-bypass-rules.json @@ -1,4 +1,123 @@ [ + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-tactic", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "attack-pattern", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "intrusion-set", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "malware", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "tool", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "campaign", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "relationship", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "identity", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "course-of-action", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "marking-definition", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-asset", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-data-source", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-data-component", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-detection-strategy", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-analytic", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-matrix", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, + { + "fieldPath": ["x_mitre_modified_by_ref"], + "errorCode": "invalid_value", + "stixType": "x-mitre-collection", + "suppressError": true, + "_comment": "This error is expected for all Workbench deployments except for the official MITRE ATT&CK deployment" + }, { "fieldPath": ["x_mitre_shortname"], "errorCode": "invalid_value", From f51ca9309b547842ed938cca177e83cc1f317e7e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:55:01 -0400 Subject: [PATCH 273/370] fix(collection-bundles): make importBundle workflow interoperable with ADM validation When bundle is imported, objects will now serialize validation errors in their entity docs. These validation errors will track the Zod errors as well as the specific ADM library version and ATTACK_SPEC_VERSION that yielded those errors. These will be returned in GET queries. Additionally, the validateContents query parameter can be set to fail closed (throw) if an ADM validation error occurs. --- .../collection-bundles-controller.js | 1 + app/exceptions/index.js | 7 +++ app/lib/error-handler.js | 4 +- app/models/subschemas/workspace.js | 15 ++++- app/repository/_base.repository.js | 8 +++ app/services/meta-classes/base.service.js | 56 ++++++++++++++++++- .../import-bundle.js | 8 ++- 7 files changed, 94 insertions(+), 5 deletions(-) diff --git a/app/controllers/collection-bundles-controller.js b/app/controllers/collection-bundles-controller.js index 79dfe2f5..665fdedc 100644 --- a/app/controllers/collection-bundles-controller.js +++ b/app/controllers/collection-bundles-controller.js @@ -244,6 +244,7 @@ exports.importBundle = async function (req, res) { const options = { previewOnly: req.query.previewOnly || req.query.checkOnly, + validateContents: req.query.validateContents === 'true', forceImportParameters, }; diff --git a/app/exceptions/index.js b/app/exceptions/index.js index 804d45b1..4928d282 100644 --- a/app/exceptions/index.js +++ b/app/exceptions/index.js @@ -255,6 +255,12 @@ class TrackNotFoundError extends CustomError { } } +class ObjectHasValidationIssuesError extends CustomError { + constructor(message = 'Object has unresolved validation issues', options) { + super(message, options); + } +} + module.exports = { //** General errors */ NotImplementedError, @@ -271,6 +277,7 @@ module.exports = { //** Validation errors */ ValidationError, SchemaValidationError, + ObjectHasValidationIssuesError, //** Revocation errors */ AlreadyRevokedError, diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index b805dd36..9fca954d 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -39,6 +39,7 @@ const { NoTaggedSnapshotsError, InvalidComponentTypeError, TrackNotFoundError, + ObjectHasValidationIssuesError, } = require('../exceptions'); exports.bodyParser = function (err, req, res, next) { @@ -126,7 +127,8 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof DuplicateNameError || err instanceof AlreadyRevokedError || err instanceof AlreadyReleasedError || - err instanceof ReleaseConflictError + err instanceof ReleaseConflictError || + err instanceof ObjectHasValidationIssuesError ) { logger.warn(`Conflict: ${err.message}`); return res.status(409).send(buildErrorResponse(err)); diff --git a/app/models/subschemas/workspace.js b/app/models/subschemas/workspace.js index 360fca63..7bedcd31 100644 --- a/app/models/subschemas/workspace.js +++ b/app/models/subschemas/workspace.js @@ -23,6 +23,13 @@ const embedddedRelationship = { }; const embeddedRelationshipSchema = new mongoose.Schema(embedddedRelationship, { _id: false }); +const validationIssue = { + message: { type: String, required: true }, + path: [String], + code: { type: String, required: true }, +}; +const validationIssueSchema = new mongoose.Schema(validationIssue, { _id: false }); + /** * Workspace property definition for most object types */ @@ -36,7 +43,13 @@ module.exports.common = { }, attack_id: String, collections: [collectionVersionSchema], - embedded_relationships: { type: [embeddedRelationshipSchema] }, + embedded_relationships: { type: [embeddedRelationshipSchema], default: undefined }, + validation: { + errors: { type: [validationIssueSchema], default: undefined }, + attack_spec_version: String, + adm_version: String, + validated_at: Date, + }, }; // x-mitre-collection workspace structure diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index b407550c..9a3443eb 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -409,6 +409,14 @@ class BaseRepository extends AbstractRepository { } } + async unsetField(documentId, fieldPath) { + try { + return await this.model.updateOne({ _id: documentId }, { $unset: { [fieldPath]: '' } }); + } catch (err) { + throw new DatabaseError(err); + } + } + async findOneAndDelete(stixId, modified) { try { return await this.model diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 35505c26..59102c5b 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -558,6 +558,20 @@ class BaseService extends ServiceWithHooks { } if (existingObject) { + // Block POST if the existing object has unresolved validation issues. + // Users must fix via PUT/updateFull first. + if (existingObject.workspace?.validation?.errors?.length > 0) { + const warning = + `Object ${data.stix.id} has unresolved validation issues. ` + + `Use PUT to update the existing version and resolve the issues before creating new versions.`; + console.warn(warning); + // TODO figure out the optimal way to treat imported objects with known validation errors + // throw new ObjectHasValidationIssuesError({ + // details: warning, + // validationErrors: existingObject.workspace.validation.errors, + // }); + } + // New version of an existing object — carry forward revoked status, set modified_by data.stix.revoked = existingObject.stix.revoked ?? false; data.stix.x_mitre_modified_by_ref = organizationIdentityRef; @@ -643,10 +657,37 @@ class BaseService extends ServiceWithHooks { data.workspace.attack_id = attackIdInExternalReferences; } - const { errors, warnings } = await this.validateComposedObject(data); + // Skip validation entirely for revoked or deprecated objects + const isRevoked = data.stix?.revoked === true; + const isDeprecated = data.stix?.x_mitre_deprecated === true; + + let errors = []; + let warnings = []; + + if (!isRevoked && !isDeprecated) { + ({ errors, warnings } = await this.validateComposedObject(data)); + } if (errors.length > 0) { - throw new ValidationError('ADM validation failed', { details: errors, warnings }); + if (options.validateContents) { + throw new ValidationError('ADM validation failed', { details: errors, warnings }); + } + + // Fail-open: store validation errors on the document + const { ATTACK_SPEC_VERSION } = require('@mitre-attack/attack-data-model'); + const admPkg = require('@mitre-attack/attack-data-model/package.json'); + + data.workspace = data.workspace || {}; + data.workspace.validation = { + errors: errors.map((e) => ({ message: e.message, path: e.path, code: e.code })), + attack_spec_version: ATTACK_SPEC_VERSION, + adm_version: admPkg.version, + validated_at: new Date(), + }; + + logger.warn( + `Import: ${data.stix.id} has ${errors.length} validation error(s), storing on document`, + ); } if (options.dryRun) { @@ -749,6 +790,12 @@ class BaseService extends ServiceWithHooks { throw new ValidationError('ADM validation failed', { details: errors, warnings }); } + // Validation passed — clear any stored validation issues from a previous import + if (document.workspace?.validation) { + data.workspace = data.workspace || {}; + data.workspace.validation = undefined; + } + // ────────────────────────────────────────────── // 6. PERSIST (skip if dry-run) // ────────────────────────────────────────────── @@ -757,6 +804,11 @@ class BaseService extends ServiceWithHooks { const newDocument = await this.repository.updateAndSave(document, data); if (newDocument === document) { + // If the document previously had validation issues, explicitly unset them + if (document.workspace?.validation !== undefined) { + await this.repository.unsetField(document._id, 'workspace.validation'); + } + await this.afterUpdate(newDocument, document); await this.emitUpdatedEvent(newDocument, document); const result = newDocument.toObject ? newDocument.toObject() : newDocument; diff --git a/app/services/stix/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js index 90778a02..02b55f5e 100644 --- a/app/services/stix/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -216,7 +216,13 @@ async function processStixObject( }; try { - await service.create(newObject, { import: true }); + // TODO should we bypass validation for imports? + // or possibly fail open on validation errors where we record the validation error on the object but still allow the import to proceed? + // for validation errors, the object may need to be placed into a quarantined state where it is visible but read-only except through a PUT operation that allows updates to be made to fix the validation errors + await service.create(newObject, { + import: true, + validateContents: options.validateContents, + }); } catch (err) { if (err.message === service.errors?.duplicateId || err instanceof DuplicateIdError) { throw err; From 823a78c395f99260d4d6b62c04b947d9ddc4734d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:56:01 -0400 Subject: [PATCH 274/370] fix: remove empty embedded_relationships and collections from workspace key is array is empty --- ...elds.js => 20260408160338-remove-empty-array-fields.js} | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) rename migrations/{20260407180930-remove-empty-array-fields.js => 20260408160338-remove-empty-array-fields.js} (96%) diff --git a/migrations/20260407180930-remove-empty-array-fields.js b/migrations/20260408160338-remove-empty-array-fields.js similarity index 96% rename from migrations/20260407180930-remove-empty-array-fields.js rename to migrations/20260408160338-remove-empty-array-fields.js index d2a6e1ab..2daa84e9 100644 --- a/migrations/20260407180930-remove-empty-array-fields.js +++ b/migrations/20260408160338-remove-empty-array-fields.js @@ -13,7 +13,12 @@ */ // Fields from stix-core.js (commonOptionalSDO) — shared across all STIX types. -const coreFields = ['stix.external_references', 'stix.object_marking_refs']; +const coreFields = [ + 'stix.external_references', + 'stix.object_marking_refs', + 'workspace.embedded_relationships', + 'workspace.collections', +]; // Type-specific fields grouped by the STIX types they apply to. // Every field lives in the `attackObjects` collection (discriminator pattern). From 660730153db4b24ab21349b77bf7c11570714ac7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:11:20 -0400 Subject: [PATCH 275/370] fix(migrations): unset each empty array field individually to avoid removing populated fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous approach used to match documents where any listed field was [], then applied to all listed fields — stripping populated arrays (e.g. external_references) from documents that only had a different field empty. --- ...0260408160338-remove-empty-array-fields.js | 120 ------------------ ...0260409141046-remove-empty-array-fields.js | 64 ++++++++++ 2 files changed, 64 insertions(+), 120 deletions(-) delete mode 100644 migrations/20260408160338-remove-empty-array-fields.js create mode 100644 migrations/20260409141046-remove-empty-array-fields.js diff --git a/migrations/20260408160338-remove-empty-array-fields.js b/migrations/20260408160338-remove-empty-array-fields.js deleted file mode 100644 index 2daa84e9..00000000 --- a/migrations/20260408160338-remove-empty-array-fields.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; - -/** - * Remove empty array values from STIX fields that previously defaulted to []. - * - * Mongoose's default behavior for `[String]` schema fields is to initialize - * them as empty arrays. The STIX 2.1 specification states that list properties - * MUST NOT be empty — they should be absent rather than present as `[]`. - * - * The corresponding model schemas have been updated to `{ type: [String], default: undefined }` - * so new documents will omit these fields when not populated. This migration - * cleans up existing documents that already have empty arrays stored. - */ - -// Fields from stix-core.js (commonOptionalSDO) — shared across all STIX types. -const coreFields = [ - 'stix.external_references', - 'stix.object_marking_refs', - 'workspace.embedded_relationships', - 'workspace.collections', -]; - -// Type-specific fields grouped by the STIX types they apply to. -// Every field lives in the `attackObjects` collection (discriminator pattern). -const fieldsByType = { - 'attack-pattern': ['stix.x_mitre_contributors'], - campaign: ['stix.aliases', 'stix.x_mitre_contributors'], - 'data-source': [ - 'stix.x_mitre_platforms', - 'stix.x_mitre_contributors', - 'stix.x_mitre_collection_layers', - ], - 'x-mitre-detection-strategy': ['stix.x_mitre_domains', 'stix.x_mitre_analytic_refs'], - 'intrusion-set': ['stix.aliases', 'stix.x_mitre_contributors'], - identity: ['stix.roles', 'stix.sectors'], - 'x-mitre-matrix': ['stix.tactic_refs'], - malware: ['stix.x_mitre_platforms', 'stix.x_mitre_contributors', 'stix.x_mitre_aliases'], - tool: ['stix.x_mitre_platforms', 'stix.x_mitre_contributors', 'stix.x_mitre_aliases'], - 'x-mitre-tactic': ['stix.x_mitre_contributors'], -}; - -module.exports = { - async up(db) { - const collection = db.collection('attackObjects'); - let totalModified = 0; - - // 1. Core fields (apply to all STIX types) - const coreOrConditions = coreFields.map((field) => ({ [field]: { $eq: [] } })); - const coreUnset = {}; - for (const field of coreFields) { - coreUnset[field] = ''; - } - - const coreResult = await collection.updateMany( - { $or: coreOrConditions }, - { $unset: coreUnset }, - ); - if (coreResult.modifiedCount > 0) { - console.log( - `Removed empty arrays from ${coreResult.modifiedCount} document(s): ${coreFields.join(', ')}`, - ); - totalModified += coreResult.modifiedCount; - } - - // 2. Type-specific fields - for (const [stixType, fields] of Object.entries(fieldsByType)) { - const orConditions = fields.map((field) => ({ [field]: { $eq: [] } })); - const filter = { - 'stix.type': stixType, - $or: orConditions, - }; - - const unsetFields = {}; - for (const field of fields) { - unsetFields[field] = ''; - } - - const result = await collection.updateMany(filter, { $unset: unsetFields }); - if (result.modifiedCount > 0) { - console.log( - `Removed empty arrays from ${result.modifiedCount} ${stixType} document(s): ${fields.join(', ')}`, - ); - totalModified += result.modifiedCount; - } - } - - console.log(`Total documents updated: ${totalModified}`); - }, - - async down(db) { - // Restore empty arrays on documents that are missing these fields. - // This is a best-effort reverse — it cannot distinguish between fields that - // were never set vs fields that were unset by the up() migration. - const collection = db.collection('attackObjects'); - - // 1. Core fields - const coreSet = {}; - const coreOrConditions = []; - for (const field of coreFields) { - coreSet[field] = []; - coreOrConditions.push({ [field]: { $exists: false } }); - } - await collection.updateMany({ $or: coreOrConditions }, { $set: coreSet }); - - // 2. Type-specific fields - for (const [stixType, fields] of Object.entries(fieldsByType)) { - const setFields = {}; - const orConditions = []; - for (const field of fields) { - setFields[field] = []; - orConditions.push({ [field]: { $exists: false } }); - } - - await collection.updateMany( - { 'stix.type': stixType, $or: orConditions }, - { $set: setFields }, - ); - } - }, -}; diff --git a/migrations/20260409141046-remove-empty-array-fields.js b/migrations/20260409141046-remove-empty-array-fields.js new file mode 100644 index 00000000..5110b10d --- /dev/null +++ b/migrations/20260409141046-remove-empty-array-fields.js @@ -0,0 +1,64 @@ +'use strict'; + +/** + * Remove empty array values from STIX fields that previously defaulted to []. + * + * Mongoose's default behavior for `[String]` schema fields is to initialize + * them as empty arrays. The STIX 2.1 specification states that list properties + * MUST NOT be empty — they should be absent rather than present as `[]`. + * + * The corresponding model schemas have been updated to `{ type: [String], default: undefined }` + * so new documents will omit these fields when not populated. This migration + * cleans up existing documents that already have empty arrays stored. + */ + +// Every array field that should be omitted rather than stored as []. +const fields = [ + 'stix.external_references', + 'stix.object_marking_refs', + 'stix.aliases', + 'stix.roles', + 'stix.sectors', + 'stix.tactic_refs', + 'stix.x_mitre_aliases', + 'stix.x_mitre_analytic_refs', + 'stix.x_mitre_collection_layers', + 'stix.x_mitre_contributors', + 'stix.x_mitre_domains', + 'stix.x_mitre_platforms', + 'workspace.embedded_relationships', + 'workspace.collections', +]; + +module.exports = { + async up(db) { + const collection = db.collection('attackObjects'); + let totalModified = 0; + + // Unset each field individually so we only remove fields that are actually + // empty arrays — not populated fields on the same document. + for (const field of fields) { + const result = await collection.updateMany( + { [field]: { $eq: [] } }, + { $unset: { [field]: '' } }, + ); + if (result.modifiedCount > 0) { + console.log(`Removed empty ${field} from ${result.modifiedCount} document(s)`); + totalModified += result.modifiedCount; + } + } + + console.log(`Total documents updated: ${totalModified}`); + }, + + async down(db) { + // Restore empty arrays on documents that are missing these fields. + // This is a best-effort reverse — it cannot distinguish between fields that + // were never set vs fields that were unset by the up() migration. + const collection = db.collection('attackObjects'); + + for (const field of fields) { + await collection.updateMany({ [field]: { $exists: false } }, { $set: { [field]: [] } }); + } + }, +}; From f559b2f5d566601c75dba9b880ea119cada6fead Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:48:22 -0400 Subject: [PATCH 276/370] fix(migrations): remove-empty-array-fields targets both attackObjects and relationships collections --- ...0260409141046-remove-empty-array-fields.js | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/migrations/20260409141046-remove-empty-array-fields.js b/migrations/20260409141046-remove-empty-array-fields.js index 5110b10d..71969f5a 100644 --- a/migrations/20260409141046-remove-empty-array-fields.js +++ b/migrations/20260409141046-remove-empty-array-fields.js @@ -30,21 +30,27 @@ const fields = [ 'workspace.collections', ]; +const collectionNames = ['attackObjects', 'relationships']; + module.exports = { async up(db) { - const collection = db.collection('attackObjects'); let totalModified = 0; // Unset each field individually so we only remove fields that are actually // empty arrays — not populated fields on the same document. - for (const field of fields) { - const result = await collection.updateMany( - { [field]: { $eq: [] } }, - { $unset: { [field]: '' } }, - ); - if (result.modifiedCount > 0) { - console.log(`Removed empty ${field} from ${result.modifiedCount} document(s)`); - totalModified += result.modifiedCount; + for (const collectionName of collectionNames) { + const collection = db.collection(collectionName); + for (const field of fields) { + const result = await collection.updateMany( + { [field]: { $eq: [] } }, + { $unset: { [field]: '' } }, + ); + if (result.modifiedCount > 0) { + console.log( + `Removed empty ${field} from ${result.modifiedCount} ${collectionName} document(s)`, + ); + totalModified += result.modifiedCount; + } } } @@ -55,10 +61,11 @@ module.exports = { // Restore empty arrays on documents that are missing these fields. // This is a best-effort reverse — it cannot distinguish between fields that // were never set vs fields that were unset by the up() migration. - const collection = db.collection('attackObjects'); - - for (const field of fields) { - await collection.updateMany({ [field]: { $exists: false } }, { $set: { [field]: [] } }); + for (const collectionName of collectionNames) { + const collection = db.collection(collectionName); + for (const field of fields) { + await collection.updateMany({ [field]: { $exists: false } }, { $set: { [field]: [] } }); + } } }, }; From b36af5cb22093dfdbb7372a481d5457738a48e3a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:58:27 -0400 Subject: [PATCH 277/370] feat: create mongo views on startup Adds app/lib/create-mongoviews.js, a direct port of create_mongo_view.py that generates the same view hierarchy: view..{all,latest}.{active,deprecated,revoked} plus union combinations for attackObjects, relationships, sdo, sro, and smo. Wired into bin/www startup script. Runs after migrations complete but before app init. The process is idempotent, meaning that existing views get updated in-place and new ones get created but only if pre-state view differs or does not exist. --- app/lib/create-mongo-views.js | 188 ++++++++++++++++++++++++++++++++++ bin/www | 11 ++ 2 files changed, 199 insertions(+) create mode 100644 app/lib/create-mongo-views.js diff --git a/app/lib/create-mongo-views.js b/app/lib/create-mongo-views.js new file mode 100644 index 00000000..54a65433 --- /dev/null +++ b/app/lib/create-mongo-views.js @@ -0,0 +1,188 @@ +'use strict'; + +const logger = require('./logger'); +const mongoose = require('mongoose'); + +/** + * Create or update a single MongoDB view. + * Uses `collMod` if the view already exists, `create` otherwise — making the operation idempotent. + */ +async function createOrUpdateView(db, viewName, viewOn, pipeline) { + const collections = await db.listCollections({ name: viewName }, { nameOnly: true }).toArray(); + const command = collections.length ? 'collMod' : 'create'; + const label = command === 'create' ? 'Creating' : 'Modifying'; + logger.info(`${label} view ${viewName}`); + await db.command({ [command]: viewName, viewOn, pipeline }); +} + +// --------------------------------------------------------------------------- +// Pipeline fragments +// --------------------------------------------------------------------------- +const SORT_DOCUMENTS = [{ $sort: { 'stix.id': 1, 'stix.modified': -1 } }]; + +const LATEST_DOCUMENTS = [ + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, +]; + +const ACTIVE_FILTER = [ + { $match: { 'stix.x_mitre_deprecated': { $in: [null, false] } } }, + { $match: { 'stix.revoked': { $in: [null, false] } } }, +]; + +const DEPRECATED_FILTER = [{ $match: { 'stix.x_mitre_deprecated': true } }]; +const REVOKED_FILTER = [{ $match: { 'stix.revoked': true } }]; + +// --------------------------------------------------------------------------- +// View generators +// --------------------------------------------------------------------------- + +/** + * Create the base set of views for a given collection (no type filter). + * - view..{all,latest} + * - view..{all,latest}.{active,deprecated,revoked} + * - view..{all,latest}.{active.deprecated,active.revoked,deprecated.revoked} + */ +async function createBaseViews(db, viewType) { + const variants = [ + { suffix: 'all', pipeline: SORT_DOCUMENTS }, + { suffix: 'all.active', pipeline: [...SORT_DOCUMENTS, ...ACTIVE_FILTER] }, + { suffix: 'all.deprecated', pipeline: [...SORT_DOCUMENTS, ...DEPRECATED_FILTER] }, + { suffix: 'all.revoked', pipeline: [...SORT_DOCUMENTS, ...REVOKED_FILTER] }, + { suffix: 'latest', pipeline: LATEST_DOCUMENTS }, + { suffix: 'latest.active', pipeline: [...LATEST_DOCUMENTS, ...ACTIVE_FILTER] }, + { suffix: 'latest.deprecated', pipeline: [...LATEST_DOCUMENTS, ...DEPRECATED_FILTER] }, + { suffix: 'latest.revoked', pipeline: [...LATEST_DOCUMENTS, ...REVOKED_FILTER] }, + ]; + + for (const { suffix, pipeline } of variants) { + await createOrUpdateView(db, `view.${viewType}.${suffix}`, viewType, pipeline); + } + + // Union views + const unionCombinations = [ + { base: 'active', extra: 'deprecated' }, + { base: 'active', extra: 'revoked' }, + { base: 'deprecated', extra: 'revoked' }, + ]; + for (const scope of ['all', 'latest']) { + for (const { base, extra } of unionCombinations) { + await createOrUpdateView( + db, + `view.${viewType}.${scope}.${base}.${extra}`, + `view.${viewType}.${scope}.${base}`, + [{ $unionWith: { coll: `view.${viewType}.${scope}.${extra}` } }], + ); + } + } +} + +/** + * Create filtered views for specific STIX types / relationship types. + */ +async function createFilteredViews(db, viewType, typeToFilter) { + for (const [itemType, mongoFilter] of Object.entries(typeToFilter)) { + const sourceCollection = ['sdo', 'smo'].includes(viewType) ? 'attackObjects' : 'relationships'; + const matchField = ['sdo', 'smo'].includes(viewType) ? 'stix.type' : 'stix.relationship_type'; + const matchStage = { $match: { [matchField]: mongoFilter } }; + + const keys = [ + { key: 'all', pipeline: [matchStage, ...SORT_DOCUMENTS] }, + { key: 'all.active', pipeline: [matchStage, ...SORT_DOCUMENTS, ...ACTIVE_FILTER] }, + { key: 'all.deprecated', pipeline: [matchStage, ...SORT_DOCUMENTS, ...DEPRECATED_FILTER] }, + { key: 'all.revoked', pipeline: [matchStage, ...SORT_DOCUMENTS, ...REVOKED_FILTER] }, + { key: 'latest', pipeline: [matchStage, ...LATEST_DOCUMENTS] }, + { key: 'latest.active', pipeline: [matchStage, ...LATEST_DOCUMENTS, ...ACTIVE_FILTER] }, + { + key: 'latest.deprecated', + pipeline: [matchStage, ...LATEST_DOCUMENTS, ...DEPRECATED_FILTER], + }, + { key: 'latest.revoked', pipeline: [matchStage, ...LATEST_DOCUMENTS, ...REVOKED_FILTER] }, + ]; + + const viewNames = {}; + for (const { key, pipeline } of keys) { + const viewName = `view.${viewType}.${key}.${itemType}`; + viewNames[key] = viewName; + await createOrUpdateView(db, viewName, sourceCollection, pipeline); + } + + // Union views + const unionCombinations = [ + { base: 'active', extra: 'deprecated' }, + { base: 'active', extra: 'revoked' }, + { base: 'deprecated', extra: 'revoked' }, + ]; + for (const scope of ['all', 'latest']) { + for (const { base, extra } of unionCombinations) { + await createOrUpdateView( + db, + `view.${viewType}.${scope}.${itemType}.${base}.${extra}`, + viewNames[`${scope}.${base}`], + [{ $unionWith: { coll: viewNames[`${scope}.${extra}`] } }], + ); + } + } + } +} + +// --------------------------------------------------------------------------- +// Public entry point +// --------------------------------------------------------------------------- + +const ATTACK_TYPE_TO_MONGO_FILTER = { + assets: 'asset', + campaigns: 'campaign', + datacomponents: 'x-mitre-data-component', + datasources: 'x-mitre-data-source', + identities: 'identity', + groups: 'intrusion-set', + matrices: 'x-mitre-matrix', + mitigations: 'course-of-action', + software: { $in: ['malware', 'tool'] }, + tactics: 'x-mitre-tactic', + techniques: 'attack-pattern', + collections: 'x-mitre-collection', + analytics: 'x-mitre-analytic', + detectionstrategies: 'x-mitre-detection-strategy', +}; + +const RELATIONSHIP_TYPES = [ + 'uses', + 'mitigates', + 'detects', + 'revoked-by', + 'subtechnique-of', + 'attributed-to', + 'targets', +]; + +/** + * Ensure all MongoDB views exist (creates or updates as needed). + * Safe to call on every startup — fully idempotent. + */ +exports.createMongoViews = async function createMongoViews() { + const db = mongoose.connection.getClient().db(); + + logger.info('Ensuring MongoDB views are up to date...'); + + // Base collection views + await createBaseViews(db, 'attackObjects'); + await createBaseViews(db, 'relationships'); + + // Filtered SDO views + await createFilteredViews(db, 'sdo', ATTACK_TYPE_TO_MONGO_FILTER); + + // Filtered SRO views + const sroFilter = {}; + for (const t of RELATIONSHIP_TYPES) { + sroFilter[t] = t; + } + await createFilteredViews(db, 'sro', sroFilter); + + // Filtered SMO views + await createFilteredViews(db, 'smo', { 'marking-definitions': 'marking-definition' }); + + logger.info('MongoDB views are up to date'); +}; diff --git a/bin/www b/bin/www index dd16592e..f09e1448 100644 --- a/bin/www +++ b/bin/www @@ -43,6 +43,17 @@ async function runServer() { throw new Error(errors.databaseMigrationFailed); } + // Ensure MongoDB views are created / up to date (idempotent) + const { createMongoViews } = require('../app/lib/create-mongo-views'); + try { + await createMongoViews(); + } catch (err) { + logger.error('Failed to create/update MongoDB views'); + logger.error(err.message); + // Non-fatal: views enhance query performance but the app can still function + logger.warn('Continuing startup despite view creation failure'); + } + // Check for valid database configuration const databaseConfiguration = require('../app/lib/database-configuration'); await databaseConfiguration.checkSystemConfiguration(); From 78339bac7a2d5f2f7ff5bc783d4b3520ac83bcb2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:17:42 -0400 Subject: [PATCH 278/370] feat: add scheduled workflows to run adm validation and statefully track in db Add backfill-workspace-validation migration script. Runs at startup. Iterates over every document in attackObjects and relationships collections. Objects that pass validation are cleared of any stale workspace.validation entries. Objects that fail result in the document's workspace.validation record creation with provenance tracking for ADM spec version and library version. Add validate-objects-task scheduled task that runs daily at 3 AM. Configurable via VALIDATE_OBJECTS_CRON. Same validation logic as the migration. --- app/config/config.js | 5 + app/scheduler/validate-objects-task.js | 195 ++++++++++++++++++ ...409142832-backfill-workspace-validation.js | 164 +++++++++++++++ 3 files changed, 364 insertions(+) create mode 100644 app/scheduler/validate-objects-task.js create mode 100644 migrations/20260409142832-backfill-workspace-validation.js diff --git a/app/config/config.js b/app/config/config.js index 5297239c..d718617a 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -255,6 +255,11 @@ function loadConfig() { default: '0 * * * *', // every hour env: 'CHECK_WIP_ATTACK_IDS_CRON', }, + validateObjectsCron: { + doc: 'Cron pattern for re-validating all STIX objects against the ADM (e.g., "0 3 * * *" for daily at 3 AM).', + default: '0 3 * * *', // daily at 3 AM + env: 'VALIDATE_OBJECTS_CRON', + }, enableScheduler: { format: Boolean, default: true, diff --git a/app/scheduler/validate-objects-task.js b/app/scheduler/validate-objects-task.js new file mode 100644 index 00000000..01777000 --- /dev/null +++ b/app/scheduler/validate-objects-task.js @@ -0,0 +1,195 @@ +'use strict'; + +const schedule = require('node-schedule'); +const logger = require('../lib/logger'); +const config = require('../config/config'); +const { getSchema } = require('../lib/validation-schemas'); + +/** + * Recursively convert Date instances to ISO strings. + * MongoDB/.lean() returns BSON Date objects, but ADM Zod schemas + * expect RFC3339 strings (z.iso.datetime). + */ +function serializeDates(obj) { + if (obj instanceof Date) return obj.toISOString(); + if (Array.isArray(obj)) return obj.map(serializeDates); + if (obj !== null && typeof obj === 'object') { + const out = {}; + for (const [key, val] of Object.entries(obj)) { + out[key] = serializeDates(val); + } + return out; + } + return obj; +} + +// Repositories for all collections that hold STIX objects +const attackObjectModel = require('../models/attack-object-model'); +const RelationshipModel = require('../models/relationship-model'); +const validationBypassesRepository = require('../repository/validation-bypasses-repository'); + +/** + * Re-validate all STIX documents and refresh `workspace.validation`. + * + * This combats "concept drift": when the ADM library or ATTACK_SPEC_VERSION + * changes, previously recorded validation results may become stale. Running + * this on a schedule keeps every document's validation metadata current. + * + * Logic mirrors BaseService._createFromImport and the backfill migration: + * - Revoked/deprecated objects skip validation (stale validation is cleared). + * - Errors that match a bypass rule are filtered out. + * - Documents that pass have workspace.validation removed. + * - Documents that fail get workspace.validation set/updated. + * + * @returns {Promise} Summary of validation results + */ +async function validateObjects() { + logger.info('[validate-objects] Starting scheduled validation of all STIX objects'); + + const { ATTACK_SPEC_VERSION } = require('@mitre-attack/attack-data-model'); + const admPkg = require('@mitre-attack/attack-data-model/package.json'); + + // Load bypass rules once for the entire run + let bypassRules = []; + try { + bypassRules = await validationBypassesRepository.findAll(); + } catch (err) { + logger.warn(`[validate-objects] Could not load bypass rules: ${err.message}`); + } + + const results = { + timestamp: new Date().toISOString(), + totalValidated: 0, + totalErrored: 0, + totalCleared: 0, + admVersion: admPkg.version, + attackSpecVersion: ATTACK_SPEC_VERSION, + }; + + // Process both collections: attackObjects (SDOs) and relationships (SROs) + const models = [ + { model: attackObjectModel, name: 'attackObjects' }, + { model: RelationshipModel, name: 'relationships' }, + ]; + + for (const { model, name } of models) { + logger.debug(`[validate-objects] Processing ${name} collection`); + + // Target ALL objects (including non-latest, revoked, and deprecated) + const cursor = model.find({}).lean().cursor(); + + for await (const doc of cursor) { + results.totalValidated++; + + const stixType = doc.stix?.type; + if (!stixType) continue; + + const status = doc.workspace?.workflow?.state || 'reviewed'; + const schema = getSchema(stixType, status); + if (!schema) continue; + + const parseResult = schema.safeParse(serializeDates(doc.stix)); + + if (parseResult.success) { + // Valid — clear any stale validation errors + if (doc.workspace?.validation) { + await model.updateOne({ _id: doc._id }, { $unset: { 'workspace.validation': '' } }); + results.totalCleared++; + } + continue; + } + + // Convert Zod issues to error objects + let errors = parseResult.error.issues.map((issue) => ({ + message: `${issue.path.join('.')} is ${issue.message}`, + path: issue.path, + code: issue.code, + })); + + // Apply bypass rules (mirrors ValidationBypassesService.checkBypassRule) + if (bypassRules.length > 0) { + errors = errors.filter((error) => { + const errorPathStr = JSON.stringify(error.path.map(String)); + return !bypassRules.some((rule) => { + if (!rule.suppressError && !rule.warningMessage) return false; + if (rule.stixType !== 'all' && rule.stixType !== stixType) return false; + if (rule.errorCode !== error.code) return false; + const rulePathStr = JSON.stringify(rule.fieldPath.map(String)); + return rulePathStr === errorPathStr; + }); + }); + } + + if (errors.length === 0) { + if (doc.workspace?.validation) { + await model.updateOne({ _id: doc._id }, { $unset: { 'workspace.validation': '' } }); + results.totalCleared++; + } + continue; + } + + // Determine if validation data has actually changed to avoid unnecessary writes + const existingValidation = doc.workspace?.validation; + const errorsChanged = + !existingValidation || + existingValidation.adm_version !== admPkg.version || + existingValidation.attack_spec_version !== ATTACK_SPEC_VERSION || + existingValidation.errors?.length !== errors.length; + + if (errorsChanged) { + await model.updateOne( + { _id: doc._id }, + { + $set: { + 'workspace.validation': { + errors: errors.map((e) => ({ message: e.message, path: e.path, code: e.code })), + attack_spec_version: ATTACK_SPEC_VERSION, + adm_version: admPkg.version, + validated_at: new Date(), + }, + }, + }, + ); + } + results.totalErrored++; + } + } + + logger.info( + `[validate-objects] Validation complete: ${results.totalValidated} documents validated, ` + + `${results.totalErrored} with errors, ${results.totalCleared} stale validations cleared ` + + `(ADM v${results.admVersion}, spec v${results.attackSpecVersion})`, + ); + + return results; +} + +/** + * Initialize and schedule this task + */ +function initializeTask() { + const cronPattern = config.scheduler.validateObjectsCron; + + logger.info(`[validate-objects] Scheduling task with cron pattern: ${cronPattern}`); + + schedule.scheduleJob(cronPattern, async () => { + try { + await validateObjects(); + } catch (err) { + logger.error(`[validate-objects] Task execution failed: ${err.message}`); + logger.error(err.stack); + } + }); + + logger.info('[validate-objects] Task scheduled successfully'); +} + +// Initialize the task when this module is loaded +if (config.scheduler.enableScheduler) { + initializeTask(); +} + +// Export for testing +module.exports = { + validateObjects, +}; diff --git a/migrations/20260409142832-backfill-workspace-validation.js b/migrations/20260409142832-backfill-workspace-validation.js new file mode 100644 index 00000000..028562b8 --- /dev/null +++ b/migrations/20260409142832-backfill-workspace-validation.js @@ -0,0 +1,164 @@ +'use strict'; + +/** + * Backfill `workspace.validation` on all STIX documents. + * + * Runs ADM (Attack Data Model) validation against every document in the + * `attackObjects` and `relationships` collections. Documents that fail + * validation get `workspace.validation` set with the error details, ADM/spec + * versions, and a timestamp. Documents that pass have any stale + * `workspace.validation` removed so only invalid documents carry the field. + * + * This ensures pre-existing and manually-created objects receive the same + * validation metadata that the import pipeline writes via + * `BaseService._createFromImport`. + */ + +const { getSchema } = require('../app/lib/validation-schemas'); + +/** + * Recursively convert Date instances to ISO strings. + * MongoDB stores timestamps as BSON Date objects, but the ADM Zod schemas + * expect RFC3339 strings (z.iso.datetime). Without this conversion, + * `created` and `modified` fields always fail with `invalid_type`. + */ +function serializeDates(obj) { + if (obj instanceof Date) return obj.toISOString(); + if (Array.isArray(obj)) return obj.map(serializeDates); + if (obj !== null && typeof obj === 'object') { + const out = {}; + for (const [key, val] of Object.entries(obj)) { + out[key] = serializeDates(val); + } + return out; + } + return obj; +} + +module.exports = { + async up(db) { + const { ATTACK_SPEC_VERSION } = require('@mitre-attack/attack-data-model'); + const admPkg = require('@mitre-attack/attack-data-model/package.json'); + + // EventBus + bypass listener may not be wired up during migrations, + // so we load the bypass rules directly for filtering. + let bypassRules = []; + try { + const bypassDocs = await db.collection('validationbypassrules').find({}).toArray(); // Mongoose pluralizes "ValidationBypassRule" + bypassRules = bypassDocs || []; + } catch { + // Collection may not exist yet — proceed without bypass rules + } + + const collections = ['attackObjects', 'relationships']; + let totalValidated = 0; + let totalErrored = 0; + let totalCleared = 0; + + for (const collectionName of collections) { + const collection = db.collection(collectionName); + // Target ALL objects (including non-latest, revoked, and deprecated) + const cursor = collection.find({}); + + while (await cursor.hasNext()) { + const doc = await cursor.next(); + totalValidated++; + + const stixType = doc.stix?.type; + if (!stixType) continue; + + const status = doc.workspace?.workflow?.state || 'reviewed'; + const schema = getSchema(stixType, status); + if (!schema) continue; + + const result = schema.safeParse(serializeDates(doc.stix)); + + if (result.success) { + // Valid — remove any stale validation errors + if (doc.workspace?.validation) { + await collection.updateOne( + { _id: doc._id }, + { $unset: { 'workspace.validation': '' } }, + ); + totalCleared++; + } + continue; + } + + // Convert Zod issues to error objects + let errors = result.error.issues.map((issue) => ({ + message: `${issue.path.join('.')} is ${issue.message}`, + path: issue.path, + code: issue.code, + })); + + // Apply bypass rules (mirrors ValidationBypassesService.checkBypassRule logic) + if (bypassRules.length > 0) { + errors = errors.filter((error) => { + const errorPathStr = JSON.stringify(error.path.map(String)); + return !bypassRules.some((rule) => { + if (!rule.suppressError && !rule.warningMessage) return false; + if (rule.stixType !== 'all' && rule.stixType !== stixType) return false; + if (rule.errorCode !== error.code) return false; + const rulePathStr = JSON.stringify(rule.fieldPath.map(String)); + return rulePathStr === errorPathStr; + }); + }); + } + + if (errors.length === 0) { + // All errors were bypassed — clear stale validation + if (doc.workspace?.validation) { + await collection.updateOne( + { _id: doc._id }, + { $unset: { 'workspace.validation': '' } }, + ); + totalCleared++; + } + continue; + } + + // Set validation errors on the document + await collection.updateOne( + { _id: doc._id }, + { + $set: { + 'workspace.validation': { + errors: errors.map((e) => ({ + message: e.message, + path: e.path, + code: e.code, + })), + attack_spec_version: ATTACK_SPEC_VERSION, + adm_version: admPkg.version, + validated_at: new Date(), + }, + }, + }, + ); + totalErrored++; + } + } + + console.log( + `Validation backfill complete: ${totalValidated} documents validated, ` + + `${totalErrored} with errors, ${totalCleared} stale validations cleared`, + ); + }, + + async down(db) { + // Remove all workspace.validation fields set by this migration + const collections = ['attackObjects', 'relationships']; + for (const collectionName of collections) { + const result = await db + .collection(collectionName) + .updateMany( + { 'workspace.validation': { $exists: true } }, + { $unset: { 'workspace.validation': '' } }, + ); + console.log( + `Removed workspace.validation from ${result.modifiedCount} document(s) in ${collectionName}`, + ); + } + }, +}; From 4e04bb3a0df4d47d3c0b3d46a4c11a79541f946e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:25:18 -0400 Subject: [PATCH 279/370] feat: add db migration script to strip empty string fields ALL documents in the database, including non-latest, revoked, and deprecated objects will be stripped of any fields which are set to empty strings. --- ...0260409122703-strip-empty-string-fields.js | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 migrations/20260409122703-strip-empty-string-fields.js diff --git a/migrations/20260409122703-strip-empty-string-fields.js b/migrations/20260409122703-strip-empty-string-fields.js new file mode 100644 index 00000000..a1e8a856 --- /dev/null +++ b/migrations/20260409122703-strip-empty-string-fields.js @@ -0,0 +1,96 @@ +'use strict'; + +/** + * Strip empty-string values from existing STIX documents. + * + * The application now drops empty-string fields at the service layer before + * persisting (BaseService.stripEmptyStrings). This migration retroactively + * cleans up any documents that already have empty-string values stored. + * + * Because empty strings can appear on any arbitrary field (top-level or nested), + * we scan each document and build a per-document $unset operation for every + * path whose value is exactly "". + */ + +const collectionNames = ['attackObjects', 'relationships']; + +/** + * Recursively collect dot-notation paths whose value is an empty string. + * + * @param {Object} obj - The (sub-)document to inspect + * @param {string} prefix - Dot-notation prefix for the current nesting level + * @returns {string[]} - Array of dot-notation paths to unset + */ +function findEmptyStringPaths(obj, prefix = '') { + const paths = []; + if (!obj || typeof obj !== 'object') return paths; + + for (const [key, val] of Object.entries(obj)) { + const fullPath = prefix ? `${prefix}.${key}` : key; + if (val === '') { + paths.push(fullPath); + } else if (val && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date)) { + paths.push(...findEmptyStringPaths(val, fullPath)); + } + } + return paths; +} + +module.exports = { + async up(db) { + let totalDocuments = 0; + let totalFields = 0; + + for (const collectionName of collectionNames) { + const collection = db.collection(collectionName); + + // Use a cursor to avoid loading the entire collection into memory. + const cursor = collection.find({}); + + const bulkOps = []; + + while (await cursor.hasNext()) { + const doc = await cursor.next(); + const paths = findEmptyStringPaths(doc); + + if (paths.length === 0) continue; + + const unsetObj = {}; + for (const p of paths) { + unsetObj[p] = ''; + } + + bulkOps.push({ + updateOne: { + filter: { _id: doc._id }, + update: { $unset: unsetObj }, + }, + }); + + totalFields += paths.length; + + // Flush in batches of 500 to limit memory usage. + if (bulkOps.length >= 500) { + const result = await collection.bulkWrite(bulkOps, { ordered: false }); + totalDocuments += result.modifiedCount; + bulkOps.length = 0; + } + } + + // Flush remaining operations. + if (bulkOps.length > 0) { + const result = await collection.bulkWrite(bulkOps, { ordered: false }); + totalDocuments += result.modifiedCount; + } + } + + console.log( + `Stripped empty-string fields from ${totalDocuments} document(s) (${totalFields} field(s) total)`, + ); + }, + + async down() { + // Cannot restore original empty-string values — they carry no meaningful data. + console.log('down migration is a no-op: empty-string values cannot be restored'); + }, +}; From df24b198bffcbb693f002a3ce390e9bc670c413b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:26:56 -0400 Subject: [PATCH 280/370] feat: strip empty string fields during create operations Also refactors a couple methods to be static. --- app/services/meta-classes/base.service.js | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 59102c5b..090b0a54 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -345,7 +345,7 @@ class BaseService extends ServiceWithHooks { * @param {boolean} [options.preserveAttackId] - If true, preserve workspace.attack_id * and ATT&CK external references (plumbing for future admin override scenarios) */ - stripServerControlledFields(data, options = {}) { + static stripServerControlledFields(data, options = {}) { const stix = data.stix; if (!stix) return; @@ -370,6 +370,25 @@ class BaseService extends ServiceWithHooks { } } + /** + * Recursively removes properties whose value is an empty string from an object. + * This prevents clients from persisting meaningless empty-string values. + * + * @param {Object} obj - Any plain object (stix, workspace, nested sub-objects) + */ + static stripEmptyStrings(obj) { + if (!obj || typeof obj !== 'object') return; + + for (const key of Object.keys(obj)) { + const val = obj[key]; + if (val === '') { + delete obj[key]; + } else if (val && typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date)) { + BaseService.stripEmptyStrings(val); + } + } + } + /** * Coerces any STIX date fields that are JavaScript Date objects into ISO-8601 strings. * @@ -381,7 +400,7 @@ class BaseService extends ServiceWithHooks { * * @param {Object} data - The request data ({ stix, workspace }) */ - normalizeDateFields(data) { + static normalizeDateFields(data) { const stix = data.stix; if (!stix) return; @@ -488,8 +507,10 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 2. COMPOSE OBJECT // ────────────────────────────────────────────── - this.stripServerControlledFields(data, options); - this.normalizeDateFields(data); + BaseService.stripServerControlledFields(data, options); + BaseService.stripEmptyStrings(data.stix); + BaseService.stripEmptyStrings(data.workspace); + BaseService.normalizeDateFields(data); data.stix.external_references = data.stix.external_references || []; // Generate or reuse the ATT&CK ID @@ -745,7 +766,9 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 2. COMPOSE OBJECT // ────────────────────────────────────────────── - this.stripServerControlledFields(data, options); + BaseService.stripServerControlledFields(data, options); + BaseService.stripEmptyStrings(data.stix); + BaseService.stripEmptyStrings(data.workspace); this.normalizeDateFields(data); // Compose server-controlled fields from existing document From bf8a79cbebc96e63a8745bca4fc0bc4a3bb45f24 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:31:28 -0400 Subject: [PATCH 281/370] fix: was calling static method via this --- app/services/meta-classes/base.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 090b0a54..e44fe97e 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -769,7 +769,7 @@ class BaseService extends ServiceWithHooks { BaseService.stripServerControlledFields(data, options); BaseService.stripEmptyStrings(data.stix); BaseService.stripEmptyStrings(data.workspace); - this.normalizeDateFields(data); + BaseService.normalizeDateFields(data); // Compose server-controlled fields from existing document data.stix.x_mitre_attack_spec_version = document.stix.x_mitre_attack_spec_version; From 8ecf44b23b4d12ebdcc14b37ae7c025fb6f00d43 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:38:01 -0400 Subject: [PATCH 282/370] fix: remove check-attack-ids-task from scheduler This was a proof-of-concept that never came to fruition. The scheduled task is no longer needed. --- app/scheduler/check-wip-attack-ids-task.js | 147 --------------------- 1 file changed, 147 deletions(-) delete mode 100644 app/scheduler/check-wip-attack-ids-task.js diff --git a/app/scheduler/check-wip-attack-ids-task.js b/app/scheduler/check-wip-attack-ids-task.js deleted file mode 100644 index 6bf525ba..00000000 --- a/app/scheduler/check-wip-attack-ids-task.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict'; - -const schedule = require('node-schedule'); -const logger = require('../lib/logger'); -const config = require('../config/config'); - -// Import all repository types that can have ATT&CK IDs -const techniquesRepository = require('../repository/techniques-repository'); -const tacticsRepository = require('../repository/tactics-repository'); -const groupsRepository = require('../repository/groups-repository'); -const softwareRepository = require('../repository/software-repository'); -const mitigationsRepository = require('../repository/mitigations-repository'); -const campaignsRepository = require('../repository/campaigns-repository'); -const dataSourcesRepository = require('../repository/data-sources-repository'); -const dataComponentsRepository = require('../repository/data-components-repository'); - -// Map of STIX types to their repositories -const STIX_TYPE_TO_REPOSITORY = { - 'attack-pattern': { repo: techniquesRepository, name: 'techniques' }, - 'x-mitre-tactic': { repo: tacticsRepository, name: 'tactics' }, - 'intrusion-set': { repo: groupsRepository, name: 'groups' }, - malware: { repo: softwareRepository, name: 'software (malware)' }, - tool: { repo: softwareRepository, name: 'software (tool)' }, - 'course-of-action': { repo: mitigationsRepository, name: 'mitigations' }, - campaign: { repo: campaignsRepository, name: 'campaigns' }, - 'x-mitre-data-source': { repo: dataSourcesRepository, name: 'data-sources' }, - 'x-mitre-data-component': { repo: dataComponentsRepository, name: 'data-components' }, -}; - -/** - * Check for work-in-progress objects that have ATT&CK IDs assigned - * - * This task identifies objects that are in "work-in-progress" state but have - * ATT&CK IDs assigned. This is a concern because: - * 1. WIP objects may never be published, wasting limited ATT&CK ID space - * 2. The numeric range for ATT&CK IDs is limited (0001-9999 per type) - * 3. Automatic ID assignment could exhaust the pool more quickly - * - * @returns {Promise} Summary of findings - */ -async function checkWipAttackIds() { - logger.info('[check-wip-attack-ids] Starting check for WIP objects with ATT&CK IDs'); - - const results = { - timestamp: new Date().toISOString(), - totalWipWithIds: 0, - byType: {}, - objects: [], - }; - - try { - // Check each repository type - for (const [, { repo, name }] of Object.entries(STIX_TYPE_TO_REPOSITORY)) { - logger.debug(`[check-wip-attack-ids] Checking ${name} objects`); - - // Query for objects in work-in-progress state - // Use retrieveAll with state filter - const queryResult = await repo.retrieveAll({ - state: 'work-in-progress', - includeRevoked: true, - includeDeprecated: true, - offset: 0, - limit: 0, // Get all results - }); - - // Extract documents from the result structure - const allWipObjects = queryResult[0]?.documents || []; - - // Filter to only those with attack_id set - const wipObjectsWithIds = allWipObjects.filter( - (obj) => obj.workspace?.attack_id && obj.workspace.attack_id !== null, - ); - - if (wipObjectsWithIds.length > 0) { - logger.warn( - `[check-wip-attack-ids] Found ${wipObjectsWithIds.length} WIP ${name} object(s) with ATT&CK IDs`, - ); - - results.byType[name] = wipObjectsWithIds.length; - results.totalWipWithIds += wipObjectsWithIds.length; - - // Add details for each object - for (const obj of wipObjectsWithIds) { - results.objects.push({ - stixId: obj.stix.id, - stixType: obj.stix.type, - attackId: obj.workspace.attack_id, - name: obj.stix.name || '(unnamed)', - modified: obj.stix.modified, - }); - } - } - } - - if (results.totalWipWithIds > 0) { - logger.warn( - `[check-wip-attack-ids] ALERT: ${results.totalWipWithIds} work-in-progress object(s) have ATT&CK IDs assigned`, - ); - logger.warn( - `[check-wip-attack-ids] This may indicate premature ID exhaustion. Consider reviewing these objects.`, - ); - - // Log summary by type - for (const [type, count] of Object.entries(results.byType)) { - logger.warn(`[check-wip-attack-ids] - ${type}: ${count}`); - } - } else { - logger.info('[check-wip-attack-ids] No WIP objects with ATT&CK IDs found'); - } - } catch (err) { - logger.error(`[check-wip-attack-ids] Error during check: ${err.message}`); - logger.error(err.stack); - throw err; - } - - logger.info('[check-wip-attack-ids] Check complete'); - return results; -} - -/** - * Initialize and schedule this task - */ -function initializeTask() { - const cronPattern = config.scheduler.checkWipAttackIdsCron; - - logger.info(`[check-wip-attack-ids] Scheduling task with cron pattern: ${cronPattern}`); - - schedule.scheduleJob(cronPattern, async () => { - try { - await checkWipAttackIds(); - } catch (err) { - logger.error(`[check-wip-attack-ids] Task execution failed: ${err.message}`); - } - }); - - logger.info('[check-wip-attack-ids] Task scheduled successfully'); -} - -// Initialize the task when this module is loaded -if (config.scheduler.enableScheduler) { - initializeTask(); -} - -// Export for testing -module.exports = { - checkWipAttackIds, -}; From 8c95d901cd1148430321939db6f29e915d558721 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 10 Apr 2026 13:39:34 -0400 Subject: [PATCH 283/370] fix: remove x_mitre_version from all SRO docs in database Adds a migration script that runs at startup. The script removes the x_mitre_version field from all preexisting relationship entities. This brings all relationships into compliance with the currently deployed ADM spec version. --- ...move-x-mitre-version-from-relationships.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 migrations/20260410171442-remove-x-mitre-version-from-relationships.js diff --git a/migrations/20260410171442-remove-x-mitre-version-from-relationships.js b/migrations/20260410171442-remove-x-mitre-version-from-relationships.js new file mode 100644 index 00000000..8ce7eb6e --- /dev/null +++ b/migrations/20260410171442-remove-x-mitre-version-from-relationships.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * Remove the `x_mitre_version` field from all relationship documents. + * + * The ATT&CK specification no longer permits `x_mitre_version` on SROs. + * Legacy relationship documents that still carry this field will fail ADM + * validation when users attempt to update them through the standard POST + * workflow. This migration retroactively strips the field so those documents + * pass validation. + * + * All relationships are updated (including deprecated ones) to ensure data + * consistency across the collection — the field is spec-invalid regardless of + * lifecycle state. + */ + +module.exports = { + async up(db) { + const collection = db.collection('relationships'); + + const result = await collection.updateMany( + { 'stix.x_mitre_version': { $exists: true } }, + { $unset: { 'stix.x_mitre_version': '' } }, + ); + + console.log(`Removed x_mitre_version from ${result.modifiedCount} relationship document(s)`); + }, + + async down() { + // Cannot restore original x_mitre_version values — they are not tracked elsewhere. + console.log('down migration is a no-op: original x_mitre_version values cannot be restored'); + }, +}; From 1c1b387a6ae089b55bb4bbcd1d6f03c6b70c9c01 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:43:29 -0400 Subject: [PATCH 284/370] fix: remove endpoint and associated functionality for getting new attack id --- app/controllers/attack-objects-controller.js | 29 ---------- app/routes/attack-objects-routes.js | 8 --- app/services/stix/attack-objects-service.js | 61 -------------------- 3 files changed, 98 deletions(-) diff --git a/app/controllers/attack-objects-controller.js b/app/controllers/attack-objects-controller.js index 263b72de..d86bf5dc 100644 --- a/app/controllers/attack-objects-controller.js +++ b/app/controllers/attack-objects-controller.js @@ -34,32 +34,3 @@ exports.retrieveAll = async function (req, res) { return res.status(500).send('Unable to get ATT&CK objects. Server error.'); } }; - -exports.getNextAttackId = async function (req, res) { - // Validate required query parameter - if (!req.query.type) { - return res.status(400).send('Missing required query parameter: type'); - } - - const stixType = req.query.type; - const parentRef = req.query.parentRef; - - // Validate parentRef for subtechniques - if (parentRef && stixType !== 'attack-pattern') { - return res.status(400).send('parentRef parameter is only valid for attack-pattern type'); - } - - try { - const nextAttackId = await attackObjectsService.getNextAttackId(stixType, parentRef); - - if (!nextAttackId) { - return res.status(400).send(`STIX type '${stixType}' does not support ATT&CK IDs`); - } - - logger.debug(`Success: Generated next ATT&CK ID ${nextAttackId} for type ${stixType}`); - return res.status(200).send({ attack_id: nextAttackId }); - } catch (err) { - logger.error('Failed to generate next ATT&CK ID with error: ' + err); - return res.status(500).send('Unable to generate next ATT&CK ID. Server error.'); - } -}; diff --git a/app/routes/attack-objects-routes.js b/app/routes/attack-objects-routes.js index 91b04a62..f9117488 100644 --- a/app/routes/attack-objects-routes.js +++ b/app/routes/attack-objects-routes.js @@ -16,12 +16,4 @@ router attackObjectsController.retrieveAll, ); -router - .route('/attack-objects/attack-id/next') - .get( - authn.authenticate, - authz.requireRole(authz.visitorOrHigher, authz.readOnlyService), - attackObjectsController.getNextAttackId, - ); - module.exports = router; diff --git a/app/services/stix/attack-objects-service.js b/app/services/stix/attack-objects-service.js index 57f0e90b..9809b635 100644 --- a/app/services/stix/attack-objects-service.js +++ b/app/services/stix/attack-objects-service.js @@ -274,67 +274,6 @@ class AttackObjectsService extends BaseService { throw new DatabaseError(err); } } - - /** - * Get the next available ATT&CK ID for a given STIX type - * @param {string} stixType - The STIX type (e.g., 'x-mitre-tactic', 'attack-pattern') - * @param {string} parentRef - Optional parent technique STIX ID (for subtechniques) - * @returns {Promise} The next available ATT&CK ID, or null if type doesn't support IDs - */ - async getNextAttackId(stixType, parentRef = null) { - const attackIdGenerator = require('../../lib/attack-id-generator'); - - // Map STIX types to their repositories - const repositoryMap = { - 'x-mitre-tactic': require('../../repository/tactics-repository'), - 'attack-pattern': require('../../repository/techniques-repository'), - 'intrusion-set': require('../../repository/groups-repository'), - malware: require('../../repository/software-repository'), - tool: require('../../repository/software-repository'), - 'course-of-action': require('../../repository/mitigations-repository'), - 'x-mitre-data-source': require('../../repository/data-sources-repository'), - 'x-mitre-data-component': require('../../repository/data-components-repository'), - 'x-mitre-asset': require('../../repository/assets-repository'), - campaign: require('../../repository/campaigns-repository'), - 'x-mitre-detection-strategy': require('../../repository/detection-strategies-repository'), - 'x-mitre-analytic': require('../../repository/analytics-repository'), - }; - - const repository = repositoryMap[stixType]; - if (!repository) { - throw new Error(`No repository found for STIX type: ${stixType}`); - } - - // Handle subtechnique ID generation - if (parentRef) { - if (stixType !== 'attack-pattern') { - throw new Error('Parent reference is only valid for attack-pattern type'); - } - - // Get parent technique to extract its ATT&CK ID - const techniquesService = require('./techniques-service'); - const parentTechniques = await techniquesService.retrieveById(parentRef, { - versions: 'latest', - }); - - if (!parentTechniques || parentTechniques.length === 0) { - throw new Error(`Parent technique not found: ${parentRef}`); - } - - const parentTechnique = parentTechniques[0]; - const parentAttackId = parentTechnique.workspace?.attack_id; - - if (!parentAttackId) { - throw new Error('Parent technique does not have an ATT&CK ID'); - } - - // Generate subtechnique ID - return await attackIdGenerator.generateAttackId(stixType, repository, true, parentAttackId); - } - - // Regular ID generation - return await attackIdGenerator.generateAttackId(stixType, repository, false); - } } module.exports.AttackObjectsService = AttackObjectsService; From 66a64ab7b4137bd3113028a247777c119f26e4d2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Sat, 11 Apr 2026 09:48:12 -0400 Subject: [PATCH 285/370] fix: add constraints on {sub,parent}<>{sub,parent} technique revoke operations - clarified the duplicate detection description to specify it checks after substitution, not before - documented that subtechnique-of rels are never transferred during preservation - added a "Techniques and Subtechniques" documentation section explaining all constraints - Added beforeRevoke hook to TechniquesService with two guards: one for blocking sub>sub (different parents), and one for blocking sub>parent (parent has children) - Added a check to the top of preservation loop in BaseService.revoke that skips subtechnique-of rels with a warning --- app/services/meta-classes/base.service.js | 29 ++++++--- app/services/stix/techniques-service.js | 72 +++++++++++++++++++++++ docs/user/revoke-workflow.md | 17 +++++- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index e44fe97e..5e1118e8 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -1004,16 +1004,15 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 6. HANDLE RELATIONSHIPS (transfer if preserveRelationships is set) // ────────────────────────────────────────────── - const existingRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( - objectA.stix.id, - ); - - // Exclude the revoked-by relationship we just created - const relationshipsToProcess = existingRelationships.filter( - (rel) => rel.stix.id !== revokedByRelationship.stix.id, - ); - if (options.preserveRelationships) { + const existingRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( + objectA.stix.id, + ); + + // Exclude the revoked-by relationship we just created + const relationshipsToProcess = existingRelationships.filter( + (rel) => rel.stix.id !== revokedByRelationship.stix.id, + ); // Build a set of relationship triples (source_ref--relationship_type--target_ref) // that Object B already participates in, so we can skip duplicates. const objectBRelationships = await relationshipsRepository.retrieveAllBySourceOrTarget( @@ -1027,6 +1026,18 @@ class BaseService extends ServiceWithHooks { for (const rel of relationshipsToProcess) { try { + // Skip subtechnique-of relationships — hierarchy relationships must be managed + // separately via the conversion endpoints, not transferred during revocation. + if (rel.stix.relationship_type === 'subtechnique-of') { + logger.info( + `Skipping subtechnique-of relationship ${rel.stix.id} during preservation (hierarchy relationships are not transferred)`, + ); + result.addWarning( + `Skipped subtechnique-of relationship ${rel.stix.id} — hierarchy relationships are not transferred during revocation`, + ); + continue; + } + // TODO here is another use case for a more robust composition layer or a DTO pattern — we are manually cloning and modifying relationship objects, which is error-prone and may not scale well if relationships have more complex fields in the future. A composition layer could handle cloning an existing relationship and substituting references while ensuring all required fields are correctly set. const relData = { ...rel }; delete relData._id; diff --git a/app/services/stix/techniques-service.js b/app/services/stix/techniques-service.js index fbff7af9..9188ef0c 100644 --- a/app/services/stix/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -169,6 +169,78 @@ class TechniquesService extends BaseService { return data.slice(startPos, endPos); } + // ============================ + // Revoke Lifecycle Hooks + // ============================ + + /** + * Validate subtechnique/parent constraints before allowing a revoke operation. + * + * Rules: + * - Sub revoking sub (different parents) → blocked (would give revoking sub two parents) + * - Sub revoking parent (parent has children) → blocked (would orphan children) + * - All other combinations are allowed; subtechnique-of relationships are excluded + * from preservation in the base revoke loop. + */ + async beforeRevoke(objectA, objectB) { + const relationshipsRepository = require('../../repository/relationships-repository'); + + const aIsSub = objectA.stix.x_mitre_is_subtechnique === true; + const bIsSub = objectB.stix.x_mitre_is_subtechnique === true; + + if (bIsSub && aIsSub) { + // Sub revoking sub — verify they share the same parent + const [aParentRels, bParentRels] = await Promise.all([ + relationshipsRepository.retrieveAll({ + sourceRef: objectA.stix.id, + relationshipType: 'subtechnique-of', + versions: 'latest', + includeRevoked: false, + includeDeprecated: false, + }), + relationshipsRepository.retrieveAll({ + sourceRef: objectB.stix.id, + relationshipType: 'subtechnique-of', + versions: 'latest', + includeRevoked: false, + includeDeprecated: false, + }), + ]); + + const aParent = aParentRels[0]?.stix.target_ref; + const bParent = bParentRels[0]?.stix.target_ref; + + if (aParent && bParent && aParent !== bParent) { + throw new BadRequestError({ + details: + `Cannot revoke subtechnique ${objectA.stix.id} with subtechnique ${objectB.stix.id}: ` + + `they belong to different parents (${aParent} vs ${bParent}). ` + + `A subtechnique can only revoke another subtechnique if they share the same parent.`, + }); + } + } + + if (bIsSub && !aIsSub) { + // Sub revoking parent — verify the parent has no child subtechniques + const childRels = await relationshipsRepository.retrieveAll({ + targetRef: objectA.stix.id, + relationshipType: 'subtechnique-of', + versions: 'latest', + includeRevoked: false, + includeDeprecated: false, + }); + + if (childRels.length > 0) { + throw new BadRequestError({ + details: + `Cannot revoke parent technique ${objectA.stix.id} with subtechnique ${objectB.stix.id}: ` + + `the parent has ${childRels.length} subtechnique(s). ` + + `Convert the revoking subtechnique to a parent technique first, or rehome the subtechniques.`, + }); + } + } + } + // ============================ // Subtechnique Conversion // ============================ diff --git a/docs/user/revoke-workflow.md b/docs/user/revoke-workflow.md index 66794909..d14a2a13 100644 --- a/docs/user/revoke-workflow.md +++ b/docs/user/revoke-workflow.md @@ -32,7 +32,22 @@ Specify the `id` and `modified` timestamp for the revoked object in the request Optionally, you can set the following query parameter to preserve relationships: -- `preserveRelationships` (boolean): If set to `true`, the workflow clones each relationship that references the revoked object so that it points to the revoking object instead, then deprecates the original. If not set or set to `false`, relationships referencing the revoked object are deprecated without being transferred. If the revoking object (Object B) already participates in a relationship with the same source, target, and relationship type as an existing relationship of the revoked object (Object A), the transfer is skipped and a warning is included in the response. +- `preserveRelationships` (boolean): If set to `true`, the workflow clones each relationship that references the revoked object so that it points to the revoking object instead, then deprecates the original. If not set or set to `false`, relationships referencing the revoked object are deprecated without being transferred. During transfer, each relationship on the revoked object (Object A) is rewritten so that Object A's STIX ID is replaced with Object B's STIX ID. If the revoking object (Object B) already participates in an equivalent relationship (i.e., one with the same source, target, and relationship type after substitution), the transfer is skipped and a warning is included in the response. Additionally, `subtechnique-of` relationships are never transferred — they are deprecated along with the other relationships but are excluded from the preservation process because transferring hierarchy relationships could create invalid parent/child states. A warning is emitted for each skipped `subtechnique-of` relationship. + +### Techniques and Subtechniques + +Both techniques (parents) and subtechniques are of STIX type `attack-pattern`, so the type check alone does not prevent cross-hierarchy revocations. The following rules apply: + +| Scenario | Allowed? | Notes | +|---|---|---| +| Parent revokes parent | Yes | Standard flow, no hierarchy concerns | +| Sub revokes sub (same parent) | Yes | `subtechnique-of` relationships are skipped during preservation (shared parent) | +| Sub revokes sub (different parent) | **No** | Would give the revoking subtechnique two parents, which is not permitted | +| Parent revokes sub | Yes | `subtechnique-of` relationships are skipped during preservation | +| Sub revokes parent (parent has no children) | Yes | `subtechnique-of` relationships are skipped during preservation | +| Sub revokes parent (parent has children) | **No** | Would orphan the parent's subtechniques; convert the subtechnique to a parent first via the conversion endpoint | + +When a revocation is blocked due to these rules, the API returns a **400 Bad Request** with a message explaining the constraint violation. ## Response From 92d337ee8bffee2d57230d13169eebf1c83effcd Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:43:36 -0400 Subject: [PATCH 286/370] fix: remove restrictions when revoking sub<>sub when subs in different parents --- app/services/stix/techniques-service.js | 33 ------------------------- docs/user/revoke-workflow.md | 2 +- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/app/services/stix/techniques-service.js b/app/services/stix/techniques-service.js index 9188ef0c..a371d2c5 100644 --- a/app/services/stix/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -177,7 +177,6 @@ class TechniquesService extends BaseService { * Validate subtechnique/parent constraints before allowing a revoke operation. * * Rules: - * - Sub revoking sub (different parents) → blocked (would give revoking sub two parents) * - Sub revoking parent (parent has children) → blocked (would orphan children) * - All other combinations are allowed; subtechnique-of relationships are excluded * from preservation in the base revoke loop. @@ -188,38 +187,6 @@ class TechniquesService extends BaseService { const aIsSub = objectA.stix.x_mitre_is_subtechnique === true; const bIsSub = objectB.stix.x_mitre_is_subtechnique === true; - if (bIsSub && aIsSub) { - // Sub revoking sub — verify they share the same parent - const [aParentRels, bParentRels] = await Promise.all([ - relationshipsRepository.retrieveAll({ - sourceRef: objectA.stix.id, - relationshipType: 'subtechnique-of', - versions: 'latest', - includeRevoked: false, - includeDeprecated: false, - }), - relationshipsRepository.retrieveAll({ - sourceRef: objectB.stix.id, - relationshipType: 'subtechnique-of', - versions: 'latest', - includeRevoked: false, - includeDeprecated: false, - }), - ]); - - const aParent = aParentRels[0]?.stix.target_ref; - const bParent = bParentRels[0]?.stix.target_ref; - - if (aParent && bParent && aParent !== bParent) { - throw new BadRequestError({ - details: - `Cannot revoke subtechnique ${objectA.stix.id} with subtechnique ${objectB.stix.id}: ` + - `they belong to different parents (${aParent} vs ${bParent}). ` + - `A subtechnique can only revoke another subtechnique if they share the same parent.`, - }); - } - } - if (bIsSub && !aIsSub) { // Sub revoking parent — verify the parent has no child subtechniques const childRels = await relationshipsRepository.retrieveAll({ diff --git a/docs/user/revoke-workflow.md b/docs/user/revoke-workflow.md index d14a2a13..2a506eff 100644 --- a/docs/user/revoke-workflow.md +++ b/docs/user/revoke-workflow.md @@ -42,7 +42,7 @@ Both techniques (parents) and subtechniques are of STIX type `attack-pattern`, s |---|---|---| | Parent revokes parent | Yes | Standard flow, no hierarchy concerns | | Sub revokes sub (same parent) | Yes | `subtechnique-of` relationships are skipped during preservation (shared parent) | -| Sub revokes sub (different parent) | **No** | Would give the revoking subtechnique two parents, which is not permitted | +| Sub revokes sub (different parent) | Yes | `subtechnique-of` relationships are skipped during preservation; the revoking sub retains its existing parent | | Parent revokes sub | Yes | `subtechnique-of` relationships are skipped during preservation | | Sub revokes parent (parent has no children) | Yes | `subtechnique-of` relationships are skipped during preservation | | Sub revokes parent (parent has children) | **No** | Would orphan the parent's subtechniques; convert the subtechnique to a parent first via the conversion endpoint | From 8076710554e96532182390552b2252ec1b996f8a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:58:13 -0400 Subject: [PATCH 287/370] feat: structured warning objects in workflow responses and verbose error logging Workflow endpoint warnings are now objects with a field and contextual metadata (relationship IDs, refs, error details) instead of flat strings. Error handler log lines now include full error payloads (details, warnings) instead of only the message. --- .../components/workflow-response.yml | 11 ++++- app/lib/error-handler.js | 14 +++---- app/lib/workflow-result.js | 8 ++-- app/services/meta-classes/base.service.js | 42 +++++++++++++++---- app/services/stix/relationships-service.js | 34 ++++++++++++--- docs/developer/workflow-response-pattern.md | 29 ++++++++++--- 6 files changed, 108 insertions(+), 30 deletions(-) diff --git a/app/api/definitions/components/workflow-response.yml b/app/api/definitions/components/workflow-response.yml index 61e08903..094578c7 100644 --- a/app/api/definitions/components/workflow-response.yml +++ b/app/api/definitions/components/workflow-response.yml @@ -32,9 +32,16 @@ components: $ref: '#/components/schemas/side-effects' warnings: type: array - description: 'Non-fatal warning messages generated during the workflow.' + description: 'Non-fatal warnings generated during the workflow. Each warning is a structured object with a message field and additional context fields.' items: - type: string + type: object + required: + - message + properties: + message: + type: string + description: 'Human-readable summary of the warning.' + additionalProperties: true example: [] side-effects: diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index 9fca954d..c7d30e31 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -103,7 +103,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof NoTaggedSnapshotsError || err instanceof InvalidComponentTypeError ) { - logger.warn(`Bad request: ${err.message}`); + logger.warn('Bad request: %s', JSON.stringify(buildErrorResponse(err))); return res.status(400).send(buildErrorResponse(err)); } @@ -116,7 +116,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof DefaultMarkingDefinitionsNotFoundError || err instanceof TrackNotFoundError ) { - logger.warn(`Not found: ${err.message}`); + logger.warn('Not found: %s', JSON.stringify(buildErrorResponse(err))); return res.status(404).send(buildErrorResponse(err)); } @@ -130,7 +130,7 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof ReleaseConflictError || err instanceof ObjectHasValidationIssuesError ) { - logger.warn(`Conflict: ${err.message}`); + logger.warn('Conflict: %s', JSON.stringify(buildErrorResponse(err))); return res.status(409).send(buildErrorResponse(err)); } @@ -142,13 +142,13 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof GenericServiceError || err instanceof DatabaseError ) { - logger.error(`Service error: ${err.message}`); + logger.error('Service error: %s', JSON.stringify(buildErrorResponse(err))); return res.status(500).send(buildErrorResponse(err)); } // Handle 502 Bad Gateway errors (external service errors) if (err instanceof HostNotFoundError || err instanceof ConnectionRefusedError) { - logger.error(`Bad gateway: ${err.message}`); + logger.error('Bad gateway: %s', JSON.stringify(buildErrorResponse(err))); return res.status(502).send(buildErrorResponse(err)); } @@ -158,13 +158,13 @@ exports.serviceExceptions = function (err, req, res, next) { err instanceof OrganizationIdentityNotSetError || err instanceof AnonymousUserAccountNotSetError ) { - logger.error(`Service unavailable: ${err.message}`); + logger.error('Service unavailable: %s', JSON.stringify(buildErrorResponse(err))); return res.status(503).send(buildErrorResponse(err)); } // Handle 501 Not Implemented errors if (err instanceof NotImplementedError) { - logger.warn(`Not implemented: ${err.message}`); + logger.warn('Not implemented: %s', JSON.stringify(buildErrorResponse(err))); return res.status(501).send(buildErrorResponse(err)); } diff --git a/app/lib/workflow-result.js b/app/lib/workflow-result.js index a9fe2f0b..182989ed 100644 --- a/app/lib/workflow-result.js +++ b/app/lib/workflow-result.js @@ -69,16 +69,16 @@ class WorkflowResult { } /** - * Add a single warning message. - * @param {string} message + * Add a single warning. + * @param {string|Object} message - Warning string or structured warning object */ addWarning(message) { this.warnings.push(message); } /** - * Add multiple warning messages. - * @param {Array} messages + * Add multiple warnings. + * @param {Array} messages - Warning strings or structured warning objects */ addWarnings(messages) { if (!Array.isArray(messages)) return; diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 5e1118e8..fbfe4d2d 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -1032,9 +1032,16 @@ class BaseService extends ServiceWithHooks { logger.info( `Skipping subtechnique-of relationship ${rel.stix.id} during preservation (hierarchy relationships are not transferred)`, ); - result.addWarning( - `Skipped subtechnique-of relationship ${rel.stix.id} — hierarchy relationships are not transferred during revocation`, - ); + result.addWarning({ + message: 'Hierarchy relationship not transferred', + reason: 'subtechnique-of', + relationship: { + id: rel.stix.id, + source_ref: rel.stix.source_ref, + target_ref: rel.stix.target_ref, + relationship_type: rel.stix.relationship_type, + }, + }); continue; } @@ -1058,9 +1065,21 @@ class BaseService extends ServiceWithHooks { logger.info( `Skipping duplicate relationship transfer: ${candidateTriple} already exists on Object B`, ); - result.addWarning( - `Skipping duplicate relationship transfer: ${candidateTriple} already exists on Object B`, - ); + result.addWarning({ + message: 'Duplicate relationship transfer skipped', + skipped: { + id: rel.stix.id, + source_ref: rel.stix.source_ref, + target_ref: rel.stix.target_ref, + relationship_type: rel.stix.relationship_type, + description: rel.stix.description, + }, + existing: { + source_ref: relData.stix.source_ref, + target_ref: relData.stix.target_ref, + relationship_type: relData.stix.relationship_type, + }, + }); continue; } @@ -1076,7 +1095,16 @@ class BaseService extends ServiceWithHooks { objectBRelTriples.add(candidateTriple); } catch (err) { logger.warn(`Failed to transfer relationship ${rel.stix.id}: ${err.message}`); - result.addWarning(`Failed to transfer relationship ${rel.stix.id}: ${err.message}`); + result.addWarning({ + message: 'Relationship transfer failed', + relationship: { + id: rel.stix.id, + source_ref: rel.stix.source_ref, + target_ref: rel.stix.target_ref, + relationship_type: rel.stix.relationship_type, + }, + error: err.message, + }); } } } diff --git a/app/services/stix/relationships-service.js b/app/services/stix/relationships-service.js index bfc743f0..1d275186 100644 --- a/app/services/stix/relationships-service.js +++ b/app/services/stix/relationships-service.js @@ -109,7 +109,15 @@ class RelationshipsService extends BaseService { logger.error( `RelationshipsService: Error creating subtechnique-of relationship for ${stixId}: ${error.message}`, ); - return { warnings: [`Failed to create subtechnique-of relationship for ${stixId}`] }; + return { + warnings: [ + { + message: 'Failed to create subtechnique-of relationship', + stixId, + error: error.message, + }, + ], + }; } } @@ -155,7 +163,11 @@ class RelationshipsService extends BaseService { logger.error( `RelationshipsService: Error deprecating relationship ${rel.stix?.id}: ${error.message}`, ); - warnings.push(`Failed to deprecate relationship ${rel.stix?.id}`); + warnings.push({ + message: 'Failed to deprecate relationship', + relationshipId: rel.stix?.id, + error: error.message, + }); } } @@ -167,7 +179,11 @@ class RelationshipsService extends BaseService { `RelationshipsService: Error handling subtechnique-to-technique conversion for ${stixId}:`, error, ); - warnings.push(`Failed to deprecate subtechnique-of relationships for ${stixId}`); + warnings.push({ + message: 'Failed to deprecate subtechnique-of relationships', + stixId, + error: error.message, + }); } return { deprecated: deprecatedDocs, warnings }; @@ -214,7 +230,11 @@ class RelationshipsService extends BaseService { ); } catch (error) { logger.error(`Failed to deprecate relationship ${rel.stix.id}: ${error.message}`); - warnings.push(`Failed to deprecate relationship ${rel.stix.id}`); + warnings.push({ + message: 'Failed to deprecate relationship', + relationshipId: rel.stix.id, + error: error.message, + }); } } @@ -223,7 +243,11 @@ class RelationshipsService extends BaseService { ); } catch (error) { logger.error(`RelationshipsService: Error handling object revoked for ${stixId}:`, error); - warnings.push(`Failed to deprecate relationships for revoked object ${stixId}`); + warnings.push({ + message: 'Failed to deprecate relationships for revoked object', + stixId, + error: error.message, + }); } return { deprecated: deprecatedDocs, warnings }; diff --git a/docs/developer/workflow-response-pattern.md b/docs/developer/workflow-response-pattern.md index fac5a60f..a6d80e42 100644 --- a/docs/developer/workflow-response-pattern.md +++ b/docs/developer/workflow-response-pattern.md @@ -23,7 +23,13 @@ Every workflow endpoint returns the same top-level shape: "deprecated": [], "deleted": { "count": 0, "stixIds": [] } }, - "warnings": [] + "warnings": [ + { + "message": "Duplicate relationship transfer skipped", + "skipped": { "id": "relationship--...", "source_ref": "...", "target_ref": "...", "relationship_type": "uses", "description": "..." }, + "existing": { "source_ref": "...", "target_ref": "...", "relationship_type": "uses" } + } + ] } ``` @@ -39,10 +45,23 @@ Every workflow endpoint returns the same top-level shape: | `sideEffects.deleted` | `object` | Hard-deleted documents. Only count + STIX IDs are returned (the documents no longer exist). | | `sideEffects.deleted.count` | `integer` | Number of deleted documents. | | `sideEffects.deleted.stixIds` | `array` | STIX IDs of deleted documents. | -| `warnings` | `array` | Non-fatal issues encountered during the workflow (e.g., a relationship that could not be deprecated). | +| `warnings` | `array` | Non-fatal issues encountered during the workflow. Each warning is a structured object with a `message` field and additional context fields specific to the warning type (see [Warning Object Schema](#warning-object-schema) below). | **Design rationale:** Counts are derivable from array lengths, so no separate summary object is needed. The `deleted` category is the sole exception because deleted documents cannot be returned in full — only their IDs survive. +### Warning Object Schema + +Every warning is an object with at least a `message` field. Additional fields vary by warning type: + +| Warning type | `message` | Additional fields | +| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| Hierarchy relationship not transferred | `"Hierarchy relationship not transferred"` | `reason`, `relationship: { id, source_ref, target_ref, relationship_type }` | +| Duplicate relationship skipped | `"Duplicate relationship transfer skipped"` | `skipped: { id, source_ref, target_ref, relationship_type, description }`, `existing: { source_ref, target_ref, relationship_type }` | +| Relationship transfer failed | `"Relationship transfer failed"` | `relationship: { id, source_ref, target_ref, relationship_type }`, `error` | +| Failed to create relationship | `"Failed to create subtechnique-of relationship"` | `stixId`, `error` | +| Failed to deprecate relationship | `"Failed to deprecate relationship"` | `relationshipId`, `error` | +| Failed to deprecate relationships (batch) | `"Failed to deprecate relationships for revoked object"` or `"Failed to deprecate subtechnique-of relationships"` | `stixId`, `error` | + ## Which Endpoints Use This Pattern | Endpoint | `workflow` value | `primary` | Typical side effects | @@ -130,7 +149,7 @@ Event handlers return a plain object with any subset of these keys: created: [ /* full documents */ ], modified: [ /* full documents */ ], deprecated: [ /* full documents */ ], - warnings: [ /* string messages */ ] + warnings: [ /* structured warning objects — each must have a `message` field */ ] } ``` @@ -139,7 +158,7 @@ Handlers that encounter errors in their catch blocks should return `{ warnings: ```javascript } catch (error) { logger.error(`Failed to deprecate relationship ${relId}: ${error.message}`); - return { warnings: [`Failed to deprecate relationship ${relId}`] }; + return { warnings: [{ message: 'Failed to deprecate relationship', relationshipId: relId, error: error.message }] }; } ``` @@ -196,7 +215,7 @@ static async handleTechniqueConvertedToSubtechnique(payload) { return { created: [rel] }; } catch (error) { logger.error(`Failed to create subtechnique-of: ${error.message}`); - return { warnings: [`Failed to create subtechnique-of relationship for ${stixId}`] }; + return { warnings: [{ message: 'Failed to create subtechnique-of relationship', stixId, error: error.message }] }; } } ``` From f66ee774bce577c1eee4b3a8d38708c2e4d13ec7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:07:50 -0400 Subject: [PATCH 288/370] feat: remove the revoked field from all relationhip entities relationships are never revoked in ATT&CK. they are only deprecated. approx. half of existing relationship documents carry a legacy stix.revoked field always set to false. its presence is a historical artifact and serves no purpose. this migration strips it from the entire collection for data consistency. --- ...80456-remove-revoked-from-relationships.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 migrations/20260413180456-remove-revoked-from-relationships.js diff --git a/migrations/20260413180456-remove-revoked-from-relationships.js b/migrations/20260413180456-remove-revoked-from-relationships.js new file mode 100644 index 00000000..6f69e28d --- /dev/null +++ b/migrations/20260413180456-remove-revoked-from-relationships.js @@ -0,0 +1,29 @@ +'use strict'; + +/** + * Remove the `revoked` field from all relationship documents. + * + * Relationships are never revoked in ATT&CK — they are only deprecated. + * Approximately half of existing relationship documents carry a legacy + * `stix.revoked` field (always set to `false`). Its presence is a + * historical artifact and serves no purpose. This migration strips it + * from the entire collection for data consistency. + */ + +module.exports = { + async up(db) { + const collection = db.collection('relationships'); + + const result = await collection.updateMany( + { 'stix.revoked': { $exists: true } }, + { $unset: { 'stix.revoked': '' } }, + ); + + console.log(`Removed revoked from ${result.modifiedCount} relationship document(s)`); + }, + + async down() { + // Cannot distinguish which documents originally had the field. + console.log('down migration is a no-op: original revoked values cannot be restored'); + }, +}; From 32c4852b7bbff14274a21dea8a24d1b03f0f3d50 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:48:29 -0400 Subject: [PATCH 289/370] fix: report SRO id and description in all response bodies for workflow operations --- app/services/meta-classes/base.service.js | 3 +++ docs/developer/workflow-response-pattern.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index fbfe4d2d..36057a76 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -1075,9 +1075,11 @@ class BaseService extends ServiceWithHooks { description: rel.stix.description, }, existing: { + id: relData.stix.id, source_ref: relData.stix.source_ref, target_ref: relData.stix.target_ref, relationship_type: relData.stix.relationship_type, + description: relData.stix.description, }, }); continue; @@ -1099,6 +1101,7 @@ class BaseService extends ServiceWithHooks { message: 'Relationship transfer failed', relationship: { id: rel.stix.id, + description: rel.stix.description, source_ref: rel.stix.source_ref, target_ref: rel.stix.target_ref, relationship_type: rel.stix.relationship_type, diff --git a/docs/developer/workflow-response-pattern.md b/docs/developer/workflow-response-pattern.md index a6d80e42..86015653 100644 --- a/docs/developer/workflow-response-pattern.md +++ b/docs/developer/workflow-response-pattern.md @@ -56,8 +56,8 @@ Every warning is an object with at least a `message` field. Additional fields va | Warning type | `message` | Additional fields | | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Hierarchy relationship not transferred | `"Hierarchy relationship not transferred"` | `reason`, `relationship: { id, source_ref, target_ref, relationship_type }` | -| Duplicate relationship skipped | `"Duplicate relationship transfer skipped"` | `skipped: { id, source_ref, target_ref, relationship_type, description }`, `existing: { source_ref, target_ref, relationship_type }` | -| Relationship transfer failed | `"Relationship transfer failed"` | `relationship: { id, source_ref, target_ref, relationship_type }`, `error` | +| Duplicate relationship skipped | `"Duplicate relationship transfer skipped"` | `skipped: { id, source_ref, target_ref, relationship_type, description }`, `existing: { id, source_ref, target_ref, relationship_type, description }` | +| Relationship transfer failed | `"Relationship transfer failed"` | `relationship: { id, description, source_ref, target_ref, relationship_type }`, `error` | | Failed to create relationship | `"Failed to create subtechnique-of relationship"` | `stixId`, `error` | | Failed to deprecate relationship | `"Failed to deprecate relationship"` | `relationshipId`, `error` | | Failed to deprecate relationships (batch) | `"Failed to deprecate relationships for revoked object"` or `"Failed to deprecate subtechnique-of relationships"` | `stixId`, `error` | From ac3684cdebaa0bb8769c7a4e6626cc4622ca2d27 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:26:50 -0400 Subject: [PATCH 290/370] refactor: optimize backfill-workspace-validation migration script --- ...409142832-backfill-workspace-validation.js | 82 +++++++++++++------ 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/migrations/20260409142832-backfill-workspace-validation.js b/migrations/20260409142832-backfill-workspace-validation.js index 028562b8..a3d117e5 100644 --- a/migrations/20260409142832-backfill-workspace-validation.js +++ b/migrations/20260409142832-backfill-workspace-validation.js @@ -40,6 +40,8 @@ module.exports = { const { ATTACK_SPEC_VERSION } = require('@mitre-attack/attack-data-model'); const admPkg = require('@mitre-attack/attack-data-model/package.json'); + const BATCH_SIZE = 500; + // EventBus + bypass listener may not be wired up during migrations, // so we load the bypass rules directly for filtering. let bypassRules = []; @@ -57,12 +59,19 @@ module.exports = { for (const collectionName of collections) { const collection = db.collection(collectionName); + const totalDocs = await collection.countDocuments({}); + let collectionValidated = 0; + + console.log(`[${collectionName}] Starting validation of ${totalDocs} documents...`); + // Target ALL objects (including non-latest, revoked, and deprecated) - const cursor = collection.find({}); + const cursor = collection.find({}).batchSize(BATCH_SIZE); + let ops = []; while (await cursor.hasNext()) { const doc = await cursor.next(); totalValidated++; + collectionValidated++; const stixType = doc.stix?.type; if (!stixType) continue; @@ -76,10 +85,12 @@ module.exports = { if (result.success) { // Valid — remove any stale validation errors if (doc.workspace?.validation) { - await collection.updateOne( - { _id: doc._id }, - { $unset: { 'workspace.validation': '' } }, - ); + ops.push({ + updateOne: { + filter: { _id: doc._id }, + update: { $unset: { 'workspace.validation': '' } }, + }, + }); totalCleared++; } continue; @@ -109,35 +120,60 @@ module.exports = { if (errors.length === 0) { // All errors were bypassed — clear stale validation if (doc.workspace?.validation) { - await collection.updateOne( - { _id: doc._id }, - { $unset: { 'workspace.validation': '' } }, - ); + ops.push({ + updateOne: { + filter: { _id: doc._id }, + update: { $unset: { 'workspace.validation': '' } }, + }, + }); totalCleared++; } continue; } // Set validation errors on the document - await collection.updateOne( - { _id: doc._id }, - { - $set: { - 'workspace.validation': { - errors: errors.map((e) => ({ - message: e.message, - path: e.path, - code: e.code, - })), - attack_spec_version: ATTACK_SPEC_VERSION, - adm_version: admPkg.version, - validated_at: new Date(), + ops.push({ + updateOne: { + filter: { _id: doc._id }, + update: { + $set: { + 'workspace.validation': { + errors: errors.map((e) => ({ + message: e.message, + path: e.path, + code: e.code, + })), + attack_spec_version: ATTACK_SPEC_VERSION, + adm_version: admPkg.version, + validated_at: new Date(), + }, }, }, }, - ); + }); totalErrored++; + + // Flush batch when it reaches BATCH_SIZE + if (ops.length >= BATCH_SIZE) { + await collection.bulkWrite(ops, { ordered: false }); + ops = []; + } + + // Progress reporting + if (collectionValidated % 10000 === 0) { + console.log( + ` [${collectionName}] ${collectionValidated} / ${totalDocs} processed ` + + `(${totalErrored} errors, ${totalCleared} cleared)`, + ); + } } + + // Flush remaining ops + if (ops.length > 0) { + await collection.bulkWrite(ops, { ordered: false }); + } + + console.log(`[${collectionName}] Done — ${collectionValidated} documents processed.`); } console.log( From b0b42e2d1ac5af26d57c3e700d00964c92102a57 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:47:54 -0400 Subject: [PATCH 291/370] fix: optimize backfill-workspace-validation migration script From bfbc877226e99476972ac8ac99350ab2bc4267d1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:09:56 -0400 Subject: [PATCH 292/370] fix: generate ATT&CK external reference for matrix objects Matrices don't have auto-generated ATT&CK IDs, so the existing external reference pipeline silently dropped their mitre-attack ref after stripping. Capture the domain-based external_id (e.g., 'enterprise-attack') from the client-provided ref before stripping, then build the correct reference with the proper URL (e.g., /matrices/enterprise instead of /matrices/enterprise-attack). --- app/lib/external-reference-builder.js | 34 ++++++++++++++++----- app/services/meta-classes/base.service.js | 37 ++++++++++++++++++++++- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/app/lib/external-reference-builder.js b/app/lib/external-reference-builder.js index 21097f5a..fb125dea 100644 --- a/app/lib/external-reference-builder.js +++ b/app/lib/external-reference-builder.js @@ -18,6 +18,7 @@ const STIX_TYPE_TO_URL_PATH = { 'x-mitre-analytic': 'analytics', 'x-mitre-asset': 'assets', 'x-mitre-tactic': 'tactics', + 'x-mitre-matrix': 'matrices', }; /** @@ -65,13 +66,20 @@ function buildAttackExternalReference(attackId, stixType, options = {}) { } let url; - const isSubtechnique = options.isSubtechnique || attackId.includes('.'); - if (stixType === 'attack-pattern' && isSubtechnique) { - // Subtechniques use format: /techniques/T1234/001 - const [parentId, subId] = attackId.split('.'); - url = `https://attack.mitre.org/${urlPath}/${parentId}/${subId}`; + if (stixType === 'x-mitre-matrix') { + // Matrices use the domain name as external_id (e.g., "enterprise-attack") + // and the URL path strips the "-attack" suffix (e.g., /matrices/enterprise) + const urlSegment = attackId.replace(/-attack$/, ''); + url = `https://attack.mitre.org/${urlPath}/${urlSegment}`; } else { - url = `https://attack.mitre.org/${urlPath}/${attackId}`; + const isSubtechnique = options.isSubtechnique || attackId.includes('.'); + if (stixType === 'attack-pattern' && isSubtechnique) { + // Subtechniques use format: /techniques/T1234/001 + const [parentId, subId] = attackId.split('.'); + url = `https://attack.mitre.org/${urlPath}/${parentId}/${subId}`; + } else { + url = `https://attack.mitre.org/${urlPath}/${attackId}`; + } } return { @@ -121,9 +129,21 @@ function extractParentDetectionStrategyId(data) { * @returns {object|null} The ATT&CK external reference object, or null if not applicable */ function createAttackExternalReference(data) { - const attackId = data.workspace?.attack_id; const stixType = data.stix?.type; + // Matrices don't have ATT&CK IDs; their external_id is the domain name + // (e.g., "enterprise-attack") taken from x_mitre_domains[0]. + if (stixType === 'x-mitre-matrix') { + const domain = data.stix?.x_mitre_domains?.[0]; + if (!domain) { + logger.debug('Matrix has no x_mitre_domains; omitting ATT&CK external reference'); + return null; + } + return buildAttackExternalReference(domain, stixType); + } + + const attackId = data.workspace?.attack_id; + if (!attackId) { // No ATT&CK ID means no external reference to generate. // This is expected for types like relationships that never have ATT&CK IDs. diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 36057a76..c3c721a5 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -5,6 +5,7 @@ const logger = require('../../lib/logger'); const config = require('../../config/config'); const attackIdGenerator = require('../../lib/attack-id-generator'); const { + buildAttackExternalReference, createAttackExternalReference, findAttackExternalReference, } = require('../../lib/external-reference-builder'); @@ -507,6 +508,17 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 2. COMPOSE OBJECT // ────────────────────────────────────────────── + + // For matrices, capture the external_id from the client-provided ATT&CK reference + // before stripping removes it. Matrices don't have auto-generated ATT&CK IDs; + // their external_id is the domain name (e.g., "enterprise-attack"). + let matrixExternalId; + if (data.stix?.type === 'x-mitre-matrix') { + matrixExternalId = + findAttackExternalReference(existingVersion?.stix?.external_references)?.external_id || + findAttackExternalReference(data.stix?.external_references)?.external_id; + } + BaseService.stripServerControlledFields(data, options); BaseService.stripEmptyStrings(data.stix); BaseService.stripEmptyStrings(data.workspace); @@ -552,7 +564,13 @@ class BaseService extends ServiceWithHooks { } // Generate and prepend the ATT&CK external reference - const attackRef = createAttackExternalReference(data); + let attackRef; + if (matrixExternalId) { + // Matrices derive their external reference from the domain name, not workspace.attack_id + attackRef = buildAttackExternalReference(matrixExternalId, data.stix.type); + } else { + attackRef = createAttackExternalReference(data); + } if (attackRef) { data.stix.external_references.unshift(attackRef); } @@ -766,6 +784,17 @@ class BaseService extends ServiceWithHooks { // ────────────────────────────────────────────── // 2. COMPOSE OBJECT // ────────────────────────────────────────────── + + // For matrices, capture the external_id before stripping removes the client-provided + // ATT&CK reference. Used as a fallback when the stored document lacks one + // (e.g., matrices created before ATT&CK ref generation was added). + let matrixExternalId; + if (data.stix?.type === 'x-mitre-matrix') { + matrixExternalId = + findAttackExternalReference(document.stix?.external_references)?.external_id || + findAttackExternalReference(data.stix?.external_references)?.external_id; + } + BaseService.stripServerControlledFields(data, options); BaseService.stripEmptyStrings(data.stix); BaseService.stripEmptyStrings(data.workspace); @@ -791,6 +820,12 @@ class BaseService extends ServiceWithHooks { const existingAttackRef = findAttackExternalReference(document.stix.external_references); if (existingAttackRef) { data.stix.external_references.unshift(existingAttackRef); + } else if (matrixExternalId) { + // Fallback for matrices created before ATT&CK ref generation was added + const matrixRef = buildAttackExternalReference(matrixExternalId, data.stix.type); + if (matrixRef) { + data.stix.external_references.unshift(matrixRef); + } } // ────────────────────────────────────────────── From f628b00a395460dd0391cd20d78eeba6ee5165f3 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:25:22 -0400 Subject: [PATCH 293/370] ci: trigger pipeline From df0a3a8c53564e08437dce9977ab022f23d032bf Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:48:44 -0400 Subject: [PATCH 294/370] feat: scope tactic shortname propagation to matching domains Tactics like 'Defense Evasion' can exist in multiple domains (Enterprise, Mobile, ICS). Previously, renaming a tactic's shortname would propagate to techniques in all domains. Now the event payload includes the tactic's x_mitre_domains, which are converted to kill_chain_names and used to filter both the retrieval query and the in-place update so only techniques in the same domain are affected. --- app/repository/techniques-repository.js | 36 ++++++++++++++--- app/services/stix/tactics-service.js | 2 + app/services/stix/techniques-service.js | 54 ++++++++++++++++++++----- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/app/repository/techniques-repository.js b/app/repository/techniques-repository.js index ef705914..2ea6df82 100644 --- a/app/repository/techniques-repository.js +++ b/app/repository/techniques-repository.js @@ -7,19 +7,34 @@ const { DatabaseError } = require('../exceptions'); class TechniqueRepository extends BaseRepository { /** * Retrieve the latest version of each technique whose kill_chain_phases contains - * a phase with the given phase_name. Returns plain objects (no _id or internal fields). + * a phase with the given phase_name (and optionally matching kill_chain_name). + * Returns plain objects (no _id or internal fields). * Used when creating new technique versions to propagate a tactic shortname change. * * @param {string} phaseName - The phase_name value to filter by + * @param {string[]} [killChainNames] - If non-empty, only match phases whose + * kill_chain_name is in this list (scopes the change to specific domains) * @returns {Promise>} Array of plain technique objects (latest version each) */ - async retrieveAllLatestByPhaseName(phaseName) { + async retrieveAllLatestByPhaseName(phaseName, killChainNames = []) { try { + const phaseMatch = + killChainNames.length > 0 + ? { + 'stix.kill_chain_phases': { + $elemMatch: { + phase_name: phaseName, + kill_chain_name: { $in: killChainNames }, + }, + }, + } + : { 'stix.kill_chain_phases.phase_name': phaseName }; + const aggregation = [ { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, { $replaceRoot: { newRoot: '$document' } }, - { $match: { 'stix.kill_chain_phases.phase_name': phaseName } }, + { $match: phaseMatch }, { $project: { _id: 0, __v: 0, __t: 0 } }, ]; return await this.model.aggregate(aggregation).exec(); @@ -33,16 +48,27 @@ class TechniqueRepository extends BaseRepository { * Called when a tactic's x_mitre_shortname changes so that connected techniques * remain consistent. * + * When killChainNames is provided, only phases whose kill_chain_name is in the + * list are updated — this prevents cross-domain propagation (e.g. an enterprise + * tactic rename should not affect mobile techniques). + * * @param {string} oldPhaseName - The previous x_mitre_shortname value * @param {string} newPhaseName - The new x_mitre_shortname value + * @param {string[]} [killChainNames] - If non-empty, restrict updates to phases + * whose kill_chain_name is in this list * @returns {Promise} */ - async updatePhaseName(oldPhaseName, newPhaseName) { + async updatePhaseName(oldPhaseName, newPhaseName, killChainNames = []) { try { + const arrayFilter = + killChainNames.length > 0 + ? { 'elem.phase_name': oldPhaseName, 'elem.kill_chain_name': { $in: killChainNames } } + : { 'elem.phase_name': oldPhaseName }; + return await this.model.updateMany( { 'stix.kill_chain_phases.phase_name': oldPhaseName }, { $set: { 'stix.kill_chain_phases.$[elem].phase_name': newPhaseName } }, - { arrayFilters: [{ 'elem.phase_name': oldPhaseName }] }, + { arrayFilters: [arrayFilter] }, ); } catch (err) { throw new DatabaseError(err); diff --git a/app/services/stix/tactics-service.js b/app/services/stix/tactics-service.js index b9c1a4e9..6bef2c81 100644 --- a/app/services/stix/tactics-service.js +++ b/app/services/stix/tactics-service.js @@ -88,6 +88,7 @@ class TacticsService extends BaseService { tacticId: document.stix.id, oldShortname, newShortname, + domains: document.stix.x_mitre_domains || [], createNewVersion: true, }); @@ -126,6 +127,7 @@ class TacticsService extends BaseService { tacticId: updatedDocument.stix.id, oldShortname, newShortname, + domains: updatedDocument.stix.x_mitre_domains || [], }); delete this._shortnameChange; diff --git a/app/services/stix/techniques-service.js b/app/services/stix/techniques-service.js index a371d2c5..73735e1c 100644 --- a/app/services/stix/techniques-service.js +++ b/app/services/stix/techniques-service.js @@ -50,13 +50,22 @@ class TechniquesService extends BaseService { * @param {string} payload.tacticId - STIX ID of the updated tactic * @param {string} payload.oldShortname - Previous x_mitre_shortname value * @param {string} payload.newShortname - New x_mitre_shortname value + * @param {string[]} payload.domains - The tactic's x_mitre_domains (e.g. ['enterprise-attack']) */ static async handleTacticShortnameChanged(payload) { - const { tacticId, oldShortname, newShortname, createNewVersion } = payload; + const { tacticId, oldShortname, newShortname, domains = [], createNewVersion } = payload; + + // Convert the tactic's domains to kill chain names so we only update + // techniques whose kill_chain_phases match both the phase_name AND the + // kill_chain_name. This prevents cross-domain propagation (e.g. an + // enterprise tactic rename bleeding into mobile techniques). + const killChainNames = domains + .map((domain) => config.domainToKillChainMap[domain]) + .filter(Boolean); logger.info( `TechniquesService: Propagating tactic shortname change '${oldShortname}' -> '${newShortname}' via ${createNewVersion ? 'new technique versions' : 'in-place update'}`, - { tacticId }, + { tacticId, killChainNames }, ); if (createNewVersion) { @@ -64,9 +73,15 @@ class TechniquesService extends BaseService { tacticId, oldShortname, newShortname, + killChainNames, ); } else { - await TechniquesService._propagateShortnameInPlace(tacticId, oldShortname, newShortname); + await TechniquesService._propagateShortnameInPlace( + tacticId, + oldShortname, + newShortname, + killChainNames, + ); } } @@ -78,25 +93,37 @@ class TechniquesService extends BaseService { * @param {string} tacticId - STIX ID of the updated tactic (for logging) * @param {string} oldShortname - Previous x_mitre_shortname value * @param {string} newShortname - New x_mitre_shortname value + * @param {string[]} killChainNames - Kill chain names derived from the tactic's domains */ - static async _propagateShortnameViaNewVersions(tacticId, oldShortname, newShortname) { - const techniques = await techniquesRepository.retrieveAllLatestByPhaseName(oldShortname); + static async _propagateShortnameViaNewVersions( + tacticId, + oldShortname, + newShortname, + killChainNames, + ) { + const techniques = await techniquesRepository.retrieveAllLatestByPhaseName( + oldShortname, + killChainNames, + ); logger.info( `TechniquesService: Creating new versions for ${techniques.length} technique(s) due to tactic shortname change`, - { tacticId, oldShortname, newShortname }, + { tacticId, oldShortname, newShortname, killChainNames }, ); for (const technique of techniques) { try { - // Clone stix shallowly — only kill_chain_phases needs to change + // Clone stix shallowly — only kill_chain_phases needs to change. + // Only rename phases that match BOTH the old phase_name AND one of the + // tactic's kill chain names, so cross-domain phases are left untouched. const newVersion = { ...technique, stix: { ...technique.stix, modified: new Date().toISOString(), kill_chain_phases: (technique.stix.kill_chain_phases || []).map((phase) => - phase.phase_name === oldShortname + phase.phase_name === oldShortname && + (killChainNames.length === 0 || killChainNames.includes(phase.kill_chain_name)) ? { ...phase, phase_name: newShortname } : { ...phase }, ), @@ -125,13 +152,18 @@ class TechniquesService extends BaseService { * @param {string} tacticId - STIX ID of the updated tactic (for logging) * @param {string} oldShortname - Previous x_mitre_shortname value * @param {string} newShortname - New x_mitre_shortname value + * @param {string[]} killChainNames - Kill chain names derived from the tactic's domains */ - static async _propagateShortnameInPlace(tacticId, oldShortname, newShortname) { + static async _propagateShortnameInPlace(tacticId, oldShortname, newShortname, killChainNames) { try { - const result = await techniquesRepository.updatePhaseName(oldShortname, newShortname); + const result = await techniquesRepository.updatePhaseName( + oldShortname, + newShortname, + killChainNames, + ); logger.info( `TechniquesService: Updated ${result.modifiedCount} technique document(s) in-place for tactic shortname change`, - { tacticId, oldShortname, newShortname }, + { tacticId, oldShortname, newShortname, killChainNames }, ); } catch (error) { logger.error( From 80a890e219c5588a8a40b2bcc9e7e4e7315b1dba Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:38:54 -0400 Subject: [PATCH 295/370] fix: write migration script to backfill workspace.embedded_relationships for latest docs --- ...6000000-backfill-embedded-relationships.js | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 migrations/20260416000000-backfill-embedded-relationships.js diff --git a/migrations/20260416000000-backfill-embedded-relationships.js b/migrations/20260416000000-backfill-embedded-relationships.js new file mode 100644 index 00000000..348ba998 --- /dev/null +++ b/migrations/20260416000000-backfill-embedded-relationships.js @@ -0,0 +1,233 @@ +'use strict'; + +/** + * Backfill `workspace.embedded_relationships` on latest documents. + * + * Legacy databases created before the embedded-relationships feature have no + * `workspace.embedded_relationships` on any document. This migration rebuilds + * them from the authoritative `_ref` / `_refs` STIX fields so that the + * Workbench UI and API consumers can resolve cross-document links without + * performing expensive joins. + * + * **Scope (Phase 1):** + * Detection Strategies → Analytics → Data Components → Data Sources + * + * Only the *latest* version of each STIX object (highest `stix.modified` per + * `stix.id`) is processed. Embedded relationships reference the full lineage + * of an object via `stix.id`; pinning to a specific version would require + * tracking `stix.modified` as well and is deferred for now. + * + * The migration is idempotent — running it multiple times produces the same + * result because it always rebuilds from the source-of-truth STIX fields. + */ + +const TARGETED_TYPES = [ + 'x-mitre-detection-strategy', + 'x-mitre-analytic', + 'x-mitre-data-component', + 'x-mitre-data-source', +]; + +const BATCH_SIZE = 500; + +/** + * Aggregation pipeline that returns one document per `stix.id` — the version + * with the most recent `stix.modified` — filtered to the STIX types we care + * about. + */ +function latestDocsPipeline(types) { + return [ + { $match: { 'stix.type': { $in: types } } }, + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { + $group: { + _id: '$stix.id', + doc: { $first: '$$ROOT' }, + }, + }, + { $replaceRoot: { newRoot: '$doc' } }, + ]; +} + +// ─── Relationship extractors ──────────────────────────────────────────────── +// Each function returns an array of { stix_id, direction } objects describing +// the *outbound* refs that a document declares via its STIX fields. + +function extractDetectionStrategyOutbound(doc) { + return (doc.stix?.x_mitre_analytic_refs || []).map((ref) => ({ + stix_id: ref, + direction: 'outbound', + })); +} + +function extractAnalyticOutbound(doc) { + return (doc.stix?.x_mitre_log_source_references || []) + .filter((ref) => ref.x_mitre_data_component_ref) + .map((ref) => ({ + stix_id: ref.x_mitre_data_component_ref, + direction: 'outbound', + })); +} + +function extractDataComponentOutbound(doc) { + const ref = doc.stix?.x_mitre_data_source_ref; + if (!ref) return []; + return [{ stix_id: ref, direction: 'outbound' }]; +} + +// ─── Reverse-index builders ───────────────────────────────────────────────── +// Build maps of targetStixId → [{ stix_id, attack_id }] so that we can +// efficiently attach *inbound* relationships to the target documents. + +function buildReverseIndex(latestDocs, sourceType, refExtractor) { + const index = new Map(); + for (const doc of latestDocs) { + if (doc.stix?.type !== sourceType) continue; + for (const outbound of refExtractor(doc)) { + if (!index.has(outbound.stix_id)) index.set(outbound.stix_id, []); + index.get(outbound.stix_id).push({ + stix_id: doc.stix.id, + attack_id: doc.workspace?.attack_id || null, + }); + } + } + return index; +} + +module.exports = { + async up(db) { + const collection = db.collection('attackObjects'); + + // ── 1. Load latest documents for all targeted types ─────────────────── + const latestDocs = await collection.aggregate(latestDocsPipeline(TARGETED_TYPES)).toArray(); + + console.log(`Loaded ${latestDocs.length} latest document(s) across targeted types`); + + // ── 2. Build lookup: stix.id → attack_id ─────────────────────────── + const attackIdLookup = new Map(); + for (const doc of latestDocs) { + attackIdLookup.set(doc.stix.id, doc.workspace?.attack_id || null); + } + + // ── 3. Build reverse indexes for inbound relationships ─────────────── + // + // Detection Strategy → Analytics (analytics receive inbound) + // Analytics → Data Components (data components receive inbound) + // Data Components → Data Sources (data sources receive inbound) + const analyticsInbound = buildReverseIndex( + latestDocs, + 'x-mitre-detection-strategy', + extractDetectionStrategyOutbound, + ); + const dataComponentsInbound = buildReverseIndex( + latestDocs, + 'x-mitre-analytic', + extractAnalyticOutbound, + ); + const dataSourcesInbound = buildReverseIndex( + latestDocs, + 'x-mitre-data-component', + extractDataComponentOutbound, + ); + + // ── 4. Build embedded_relationships per document ───────────────────── + let ops = []; + const counts = { set: 0, unset: 0 }; + + for (const doc of latestDocs) { + const type = doc.stix?.type; + const relationships = []; + + // ─ Outbound ─ + let outbound = []; + if (type === 'x-mitre-detection-strategy') { + outbound = extractDetectionStrategyOutbound(doc); + } else if (type === 'x-mitre-analytic') { + outbound = extractAnalyticOutbound(doc); + } else if (type === 'x-mitre-data-component') { + outbound = extractDataComponentOutbound(doc); + } + // Data sources have no outbound refs in this scope + + for (const rel of outbound) { + relationships.push({ + stix_id: rel.stix_id, + attack_id: attackIdLookup.get(rel.stix_id) || null, + direction: 'outbound', + }); + } + + // ─ Inbound ─ + let inbound = []; + if (type === 'x-mitre-analytic') { + inbound = analyticsInbound.get(doc.stix.id) || []; + } else if (type === 'x-mitre-data-component') { + inbound = dataComponentsInbound.get(doc.stix.id) || []; + } else if (type === 'x-mitre-data-source') { + inbound = dataSourcesInbound.get(doc.stix.id) || []; + } + + for (const rel of inbound) { + relationships.push({ + stix_id: rel.stix_id, + attack_id: rel.attack_id, + direction: 'inbound', + }); + } + + // ─ Write ─ + if (relationships.length > 0) { + ops.push({ + updateOne: { + filter: { _id: doc._id }, + update: { $set: { 'workspace.embedded_relationships': relationships } }, + }, + }); + counts.set++; + } else { + // No relationships — ensure stale data is cleared (idempotency) + ops.push({ + updateOne: { + filter: { _id: doc._id, 'workspace.embedded_relationships': { $exists: true } }, + update: { $unset: { 'workspace.embedded_relationships': '' } }, + }, + }); + counts.unset++; + } + + // Flush batch + if (ops.length >= BATCH_SIZE) { + await collection.bulkWrite(ops, { ordered: false }); + ops = []; + } + } + + // Flush remaining ops + if (ops.length > 0) { + await collection.bulkWrite(ops, { ordered: false }); + } + + console.log( + `Backfill complete: set embedded_relationships on ${counts.set} document(s), ` + + `cleared stale data on up to ${counts.unset} document(s)`, + ); + }, + + async down(db) { + // Remove all embedded_relationships set by this migration for the targeted types + const collection = db.collection('attackObjects'); + + for (const type of TARGETED_TYPES) { + const result = await collection.updateMany( + { + 'stix.type': type, + 'workspace.embedded_relationships': { $exists: true }, + }, + { $unset: { 'workspace.embedded_relationships': '' } }, + ); + console.log( + `Removed workspace.embedded_relationships from ${result.modifiedCount} ${type} document(s)`, + ); + } + }, +}; From fce2b1dce17f0dbdf2509ac72e5ad9d5ad25645c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Apr 2026 11:39:53 -0400 Subject: [PATCH 296/370] ci: trigger pipeline From 47392eb87cbe01fd56afbdfdd162946a123051ef Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:49:24 -0400 Subject: [PATCH 297/370] fix(stix): preserve embedded relationships on POST version creation Keep server-managed embedded relationships when POST creates a new version of a detection strategy or analytic and the request payload omits workspace metadata. Rebuild only the type-specific outbound relationships from the new payload while carrying forward unrelated persisted relationships. --- app/services/stix/analytics-service.js | 22 ++++++++- .../stix/detection-strategies-service.js | 16 ++++-- app/tests/api/analytics/analytics.spec.js | 48 ++++++++++++++++++ .../detection-strategies-spec.js | 49 +++++++++++++++++++ 4 files changed, 129 insertions(+), 6 deletions(-) diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index c74e2b8b..7f7245aa 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -229,11 +229,31 @@ class AnalyticsService extends BaseService { data.workspace.embedded_relationships = []; } + // Check if this is a new version of an existing analytic. + let previousVersion = null; + if (data.stix?.id) { + try { + previousVersion = await this.repository.retrieveLatestByStixId(data.stix.id); + } catch { + logger.debug(`No previous version found for analytic ${data.stix.id}`); + } + } + // Build outbound embedded_relationships for data component references - // Cross-repository READS are allowed for denormalization (see CROSS_SERVICE_READS_PATTERN.md) const dataComponentRefs = data.stix?.x_mitre_log_source_references?.map((ref) => ref.x_mitre_data_component_ref) || []; + // Preserve non-data-component relationships from the previous persisted version when POST + // is creating a new version. Client payloads often omit server-managed workspace metadata. + const baselineEmbeddedRelationships = + previousVersion?.workspace?.embedded_relationships || + data.workspace.embedded_relationships || + []; + const existingNonDataComponentRels = baselineEmbeddedRelationships.filter( + (rel) => !rel.stix_id?.startsWith('x-mitre-data-component--'), + ); + data.workspace.embedded_relationships = [...existingNonDataComponentRels]; + if (dataComponentRefs.length > 0) { for (const dataComponentId of dataComponentRefs) { const dataComponent = diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index f4af0e97..b3715eeb 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -56,7 +56,7 @@ class DetectionStrategiesService extends BaseService { let previousVersion = null; if (data.stix?.id) { try { - previousVersion = await detectionStrategiesRepository.retrieveLatestByStixId(data.stix.id); + previousVersion = await this.repository.retrieveLatestByStixId(data.stix.id); } catch { // It's okay if there's no previous version - this might be the first version logger.debug(`No previous version found for detection strategy ${data.stix.id}`); @@ -75,10 +75,16 @@ class DetectionStrategiesService extends BaseService { this._removedAnalyticRefs = oldAnalyticRefs.filter((ref) => !newAnalyticRefs.includes(ref)); } - // Preserve non-analytic embedded_relationships and rebuild only analytic refs - // This ensures stale analytic relationships from previous versions are not carried over - // while preserving any other embedded relationships that may exist - const existingNonAnalyticRels = (data.workspace.embedded_relationships || []).filter( + // Preserve non-analytic embedded_relationships from the previous version when POST is + // creating a new version. Client payloads often omit server-managed workspace metadata, + // so using the request body here would drop unrelated relationships on version creation. + const baselineEmbeddedRelationships = + previousVersion?.workspace?.embedded_relationships || + data.workspace.embedded_relationships || + []; + + // Rebuild only the analytic outbound relationships for the new version. + const existingNonAnalyticRels = baselineEmbeddedRelationships.filter( (rel) => !rel.stix_id?.startsWith('x-mitre-analytic--'), ); diff --git a/app/tests/api/analytics/analytics.spec.js b/app/tests/api/analytics/analytics.spec.js index 01313d41..6a361006 100644 --- a/app/tests/api/analytics/analytics.spec.js +++ b/app/tests/api/analytics/analytics.spec.js @@ -4,6 +4,7 @@ const _ = require('lodash'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); +const analyticsRepository = require('../../../repository/analytics-repository'); const config = require('../../../config/config'); const login = require('../../shared/login'); @@ -246,6 +247,53 @@ describe('Analytics API', function () { expect(analytic3).toBeDefined(); }); + it('POST /api/analytics preserves inbound embedded relationships when creating a new version', async function () { + const latestAnalytic = await analyticsRepository.retrieveLatestByStixId(analytic3.stix.id); + + latestAnalytic.workspace.embedded_relationships = [ + ...(latestAnalytic.workspace?.embedded_relationships || []), + { + stix_id: 'x-mitre-detection-strategy--00000000-0000-4000-8000-000000000001', + attack_id: 'DET-TEST', + direction: 'inbound', + }, + ]; + await analyticsRepository.saveDocument(latestAnalytic); + + const nextVersion = cloneForCreate( + latestAnalytic.toObject ? latestAnalytic.toObject() : latestAnalytic, + ); + delete nextVersion.workspace.embedded_relationships; + nextVersion.stix.modified = new Date(Date.now() + 1000).toISOString(); + + const res = await request(app) + .post('/api/analytics') + .send(nextVersion) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + const analytic = res.body; + expect(analytic.workspace.embedded_relationships).toBeDefined(); + expect(analytic.workspace.embedded_relationships).toHaveLength(1); + expect(analytic.workspace.embedded_relationships[0]).toEqual( + expect.objectContaining({ + stix_id: 'x-mitre-detection-strategy--00000000-0000-4000-8000-000000000001', + attack_id: 'DET-TEST', + direction: 'inbound', + }), + ); + + const attackRef = analytic.stix.external_references.find( + (ref) => ref.source_name === 'mitre-attack', + ); + expect(attackRef).toBeDefined(); + expect(attackRef.url).toBe( + `https://attack.mitre.org/detectionstrategies/DET-TEST#${analytic.workspace.attack_id}`, + ); + }); + it('GET /api/analytics returns the latest added analytic', async function () { const res = await request(app) .get('/api/analytics/' + analytic3.stix.id) diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index ad236a8a..240d8884 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -3,6 +3,7 @@ const { expect } = require('expect'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); +const detectionStrategiesRepository = require('../../../repository/detection-strategies-repository'); const config = require('../../../config/config'); const login = require('../../shared/login'); @@ -332,6 +333,54 @@ describe('Detection Strategies API', function () { expect(detectionStrategy).toBeDefined(); }); + it('POST /api/detection-strategies preserves non-analytic embedded relationships when creating a new version', async function () { + const latestDetectionStrategy = await detectionStrategiesRepository.retrieveLatestByStixId( + detectionStrategy3.stix.id, + ); + + latestDetectionStrategy.workspace.embedded_relationships = [ + ...(latestDetectionStrategy.workspace?.embedded_relationships || []), + { + stix_id: 'x-mitre-data-component--00000000-0000-4000-8000-000000000001', + attack_id: 'DCP-TEST', + direction: 'inbound', + }, + ]; + await detectionStrategiesRepository.saveDocument(latestDetectionStrategy); + + const nextVersion = cloneForCreate( + latestDetectionStrategy.toObject + ? latestDetectionStrategy.toObject() + : latestDetectionStrategy, + ); + delete nextVersion.workspace.embedded_relationships; + nextVersion.stix.modified = new Date(Date.now() + 1000).toISOString(); + + const res = await request(app) + .post('/api/detection-strategies') + .send(nextVersion) + .set('Accept', 'application/json') + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) + .expect(201) + .expect('Content-Type', /json/); + + const detectionStrategy = res.body; + expect(detectionStrategy.workspace.embedded_relationships).toBeDefined(); + expect(detectionStrategy.workspace.embedded_relationships).toHaveLength(3); + + const preservedRel = detectionStrategy.workspace.embedded_relationships.find( + (rel) => rel.stix_id === 'x-mitre-data-component--00000000-0000-4000-8000-000000000001', + ); + expect(preservedRel).toBeDefined(); + expect(preservedRel.direction).toBe('inbound'); + expect(preservedRel.attack_id).toBe('DCP-TEST'); + + const analyticRels = detectionStrategy.workspace.embedded_relationships.filter((rel) => + rel.stix_id?.startsWith('x-mitre-analytic--'), + ); + expect(analyticRels).toHaveLength(2); + }); + it('GET /api/detection-strategies returns the latest added detection strategy', async function () { const res = await request(app) .get('/api/detection-strategies/' + detectionStrategy3.stix.id) From 409e4ca51605f75dccef97cd8456ce7af5b41dcf Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:50:01 -0400 Subject: [PATCH 298/370] fix(external-refs): preserve analytic ATT&CK URLs across versioned POSTs Make analytic ATT&CK external reference generation fall back to the previous persisted version when the POST payload omits inbound embedded relationships. This keeps detection strategy URLs intact when creating a new analytic version. --- app/lib/external-reference-builder.js | 55 +++++++++++++---------- app/services/meta-classes/base.service.js | 2 +- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/app/lib/external-reference-builder.js b/app/lib/external-reference-builder.js index fb125dea..e8fee40c 100644 --- a/app/lib/external-reference-builder.js +++ b/app/lib/external-reference-builder.js @@ -92,31 +92,37 @@ function buildAttackExternalReference(attackId, stixType, options = {}) { /** * Extract parent detection strategy ATT&CK ID from workspace embedded relationships * @param {object} data - The data object containing workspace + * @param {object} options - Optional fallback context + * @param {object} options.previousVersion - Previous persisted version for version-aware lookups * @returns {string|null} The detection strategy ATT&CK ID or null */ -function extractParentDetectionStrategyId(data) { +function extractParentDetectionStrategyId(data, options = {}) { // Check if this is an analytic if (data.stix?.type !== 'x-mitre-analytic') { return null; } - // Look for parent detection strategy in embedded relationships - // Analytics are referenced by detection strategies via x_mitre_analytic_refs - // So we look for inbound relationships from detection strategies - const embeddedRelationships = data.workspace?.embedded_relationships; - if ( - embeddedRelationships && - Array.isArray(embeddedRelationships) && - embeddedRelationships.length > 0 - ) { - // Find any inbound relationship from a detection strategy - const parentDetectionStrategy = embeddedRelationships.find( - (rel) => - rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--'), - ); - - if (parentDetectionStrategy) { - return parentDetectionStrategy.attack_id || null; + const embeddedRelationshipCandidates = [ + data.workspace?.embedded_relationships, + options.previousVersion?.workspace?.embedded_relationships, + ]; + + // Look for parent detection strategy in embedded relationships. Analytics are referenced + // by detection strategies via x_mitre_analytic_refs, so we look for inbound relationships. + for (const embeddedRelationships of embeddedRelationshipCandidates) { + if ( + embeddedRelationships && + Array.isArray(embeddedRelationships) && + embeddedRelationships.length > 0 + ) { + const parentDetectionStrategy = embeddedRelationships.find( + (rel) => + rel.direction === 'inbound' && rel.stix_id?.startsWith('x-mitre-detection-strategy--'), + ); + + if (parentDetectionStrategy) { + return parentDetectionStrategy.attack_id || null; + } } } @@ -126,9 +132,11 @@ function extractParentDetectionStrategyId(data) { /** * Create an ATT&CK external reference for the given data * @param {object} data - The data object containing stix and workspace + * @param {object} options - Optional context for deriving the reference + * @param {object} options.previousVersion - Previous persisted version for version-aware lookups * @returns {object|null} The ATT&CK external reference object, or null if not applicable */ -function createAttackExternalReference(data) { +function createAttackExternalReference(data, options = {}) { const stixType = data.stix?.type; // Matrices don't have ATT&CK IDs; their external_id is the domain name @@ -151,15 +159,16 @@ function createAttackExternalReference(data) { } // Prepare options for URL building - const options = {}; + const buildOptions = {}; if (stixType === 'attack-pattern') { - options.isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; + buildOptions.isSubtechnique = data.stix?.x_mitre_is_subtechnique === true; } if (stixType === 'x-mitre-analytic') { - options.parentDetectionStrategyId = extractParentDetectionStrategyId(data); + buildOptions.parentDetectionStrategyId = + options.parentDetectionStrategyId || extractParentDetectionStrategyId(data, options); } - return buildAttackExternalReference(attackId, stixType, options); + return buildAttackExternalReference(attackId, stixType, buildOptions); } /** diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index c3c721a5..c5421cec 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -569,7 +569,7 @@ class BaseService extends ServiceWithHooks { // Matrices derive their external reference from the domain name, not workspace.attack_id attackRef = buildAttackExternalReference(matrixExternalId, data.stix.type); } else { - attackRef = createAttackExternalReference(data); + attackRef = createAttackExternalReference(data, { previousVersion: existingVersion }); } if (attackRef) { data.stix.external_references.unshift(attackRef); From 5851751f8db366068c12319a6f70c064aae59de8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:55:00 -0400 Subject: [PATCH 299/370] test: update regressions to align with fixed embedded relationship handling logic --- app/tests/api/analytics/analytics.spec.js | 23 ++++++++++--------- .../detection-strategies-spec.js | 21 +++++++++-------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/app/tests/api/analytics/analytics.spec.js b/app/tests/api/analytics/analytics.spec.js index 6a361006..fea3c096 100644 --- a/app/tests/api/analytics/analytics.spec.js +++ b/app/tests/api/analytics/analytics.spec.js @@ -247,6 +247,7 @@ describe('Analytics API', function () { expect(analytic3).toBeDefined(); }); + let analytic4; it('POST /api/analytics preserves inbound embedded relationships when creating a new version', async function () { const latestAnalytic = await analyticsRepository.retrieveLatestByStixId(analytic3.stix.id); @@ -274,10 +275,10 @@ describe('Analytics API', function () { .expect(201) .expect('Content-Type', /json/); - const analytic = res.body; - expect(analytic.workspace.embedded_relationships).toBeDefined(); - expect(analytic.workspace.embedded_relationships).toHaveLength(1); - expect(analytic.workspace.embedded_relationships[0]).toEqual( + analytic4 = res.body; + expect(analytic4.workspace.embedded_relationships).toBeDefined(); + expect(analytic4.workspace.embedded_relationships).toHaveLength(1); + expect(analytic4.workspace.embedded_relationships[0]).toEqual( expect.objectContaining({ stix_id: 'x-mitre-detection-strategy--00000000-0000-4000-8000-000000000001', attack_id: 'DET-TEST', @@ -285,18 +286,18 @@ describe('Analytics API', function () { }), ); - const attackRef = analytic.stix.external_references.find( + const attackRef = analytic4.stix.external_references.find( (ref) => ref.source_name === 'mitre-attack', ); expect(attackRef).toBeDefined(); expect(attackRef.url).toBe( - `https://attack.mitre.org/detectionstrategies/DET-TEST#${analytic.workspace.attack_id}`, + `https://attack.mitre.org/detectionstrategies/DET-TEST#${analytic4.workspace.attack_id}`, ); }); it('GET /api/analytics returns the latest added analytic', async function () { const res = await request(app) - .get('/api/analytics/' + analytic3.stix.id) + .get('/api/analytics/' + analytic4.stix.id) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -308,8 +309,8 @@ describe('Analytics API', function () { expect(Array.isArray(analytics)).toBe(true); expect(analytics.length).toBe(1); const analytic = analytics[0]; - expect(analytic.stix.id).toBe(analytic3.stix.id); - expect(analytic.stix.modified).toBe(analytic3.stix.modified); + expect(analytic.stix.id).toBe(analytic4.stix.id); + expect(analytic.stix.modified).toBe(analytic4.stix.modified); }); it('GET /api/analytics returns all added analytics', async function () { @@ -320,11 +321,11 @@ describe('Analytics API', function () { .expect(200) .expect('Content-Type', /json/); - // We expect to get two analytics in an array + // We expect to get four analytics in an array const analytics = res.body; expect(analytics).toBeDefined(); expect(Array.isArray(analytics)).toBe(true); - expect(analytics.length).toBe(3); + expect(analytics.length).toBe(4); }); it('GET /api/analytics/:id/modified/:modified returns the first added analytic', async function () { diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index 240d8884..72595866 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -333,6 +333,7 @@ describe('Detection Strategies API', function () { expect(detectionStrategy).toBeDefined(); }); + let detectionStrategy4; it('POST /api/detection-strategies preserves non-analytic embedded relationships when creating a new version', async function () { const latestDetectionStrategy = await detectionStrategiesRepository.retrieveLatestByStixId( detectionStrategy3.stix.id, @@ -364,18 +365,18 @@ describe('Detection Strategies API', function () { .expect(201) .expect('Content-Type', /json/); - const detectionStrategy = res.body; - expect(detectionStrategy.workspace.embedded_relationships).toBeDefined(); - expect(detectionStrategy.workspace.embedded_relationships).toHaveLength(3); + detectionStrategy4 = res.body; + expect(detectionStrategy4.workspace.embedded_relationships).toBeDefined(); + expect(detectionStrategy4.workspace.embedded_relationships).toHaveLength(3); - const preservedRel = detectionStrategy.workspace.embedded_relationships.find( + const preservedRel = detectionStrategy4.workspace.embedded_relationships.find( (rel) => rel.stix_id === 'x-mitre-data-component--00000000-0000-4000-8000-000000000001', ); expect(preservedRel).toBeDefined(); expect(preservedRel.direction).toBe('inbound'); expect(preservedRel.attack_id).toBe('DCP-TEST'); - const analyticRels = detectionStrategy.workspace.embedded_relationships.filter((rel) => + const analyticRels = detectionStrategy4.workspace.embedded_relationships.filter((rel) => rel.stix_id?.startsWith('x-mitre-analytic--'), ); expect(analyticRels).toHaveLength(2); @@ -383,7 +384,7 @@ describe('Detection Strategies API', function () { it('GET /api/detection-strategies returns the latest added detection strategy', async function () { const res = await request(app) - .get('/api/detection-strategies/' + detectionStrategy3.stix.id) + .get('/api/detection-strategies/' + detectionStrategy4.stix.id) .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -395,8 +396,8 @@ describe('Detection Strategies API', function () { expect(Array.isArray(detectionStrategies)).toBe(true); expect(detectionStrategies.length).toBe(1); const detectionStrategy = detectionStrategies[0]; - expect(detectionStrategy.stix.id).toBe(detectionStrategy3.stix.id); - expect(detectionStrategy.stix.modified).toBe(detectionStrategy3.stix.modified); + expect(detectionStrategy.stix.id).toBe(detectionStrategy4.stix.id); + expect(detectionStrategy.stix.modified).toBe(detectionStrategy4.stix.modified); }); it('GET /api/detection-strategies returns all added detection strategies', async function () { @@ -407,11 +408,11 @@ describe('Detection Strategies API', function () { .expect(200) .expect('Content-Type', /json/); - // We expect to get two detection strategies in an array + // We expect to get four detection strategies in an array const detectionStrategies = res.body; expect(detectionStrategies).toBeDefined(); expect(Array.isArray(detectionStrategies)).toBe(true); - expect(detectionStrategies.length).toBe(3); + expect(detectionStrategies.length).toBe(4); }); it('GET /api/detection-strategies/:id/modified/:modified returns the first added detection strategy', async function () { From e06f0a17ee48ed7919acb7b93360868c9e6f794c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:59:08 -0400 Subject: [PATCH 300/370] fix: stop throwing exception when parentTechniqueId query param is omitted This workflow has not been implemented on the backend yet. Log a warning instead. --- app/services/meta-classes/base.service.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index c5421cec..1606f29b 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -16,7 +16,7 @@ const { InvalidQueryStringParameterError, InvalidTypeError, OrganizationIdentityNotSetError, - InvalidPostOperationError, + //InvalidPostOperationError, ValidationError, BadRequestError, NotFoundError, @@ -539,14 +539,22 @@ class BaseService extends ServiceWithHooks { // Validate subtechnique requirements if (isSubtechnique && !parentTechniqueId) { - throw new InvalidPostOperationError( + logger.warn( 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).', ); + // TODO start throwing after migrating workflow to BE + // throw new InvalidPostOperationError( + // 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).', + // ); } if (!isSubtechnique && parentTechniqueId) { - throw new InvalidPostOperationError( + logger.warn( 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).', ); + // TODO start throwing after migrating workflow to BE + // throw new InvalidPostOperationError( + // 'parentTechniqueId query parameter is only valid for subtechniques (x_mitre_is_subtechnique: true).', + // ); } // Generate a new ATT&CK ID From 5f20eb48ddd7bde6e6d52a8b9ece6ab2a168d16a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:59:59 -0400 Subject: [PATCH 301/370] fix: reset created + modified on preserved relationships during revoke operations --- app/services/meta-classes/base.service.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 1606f29b..0d35a50f 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -1094,6 +1094,10 @@ class BaseService extends ServiceWithHooks { delete relData.__v; delete relData.__t; + // Reset timestamps + relData.stix.created = now; + relData.stix.modified = now; + // Substitute Object B for Object A if (relData.stix.source_ref === objectA.stix.id) { relData.stix.source_ref = objectB.stix.id; From 72a57a937ad061e98785a136389151efc83c2b31 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:02:26 -0400 Subject: [PATCH 302/370] ci: trigger pipeline From 92325cc083a72ca7c99cffddfe85825921a3ee7f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:30:58 -0400 Subject: [PATCH 303/370] fix(errors): normalize custom exception serialization and wrapped error handling --- app/exceptions/index.js | 75 +++++++++++++-- app/repository/user-accounts-repository.js | 2 +- app/services/stix/collections-service.js | 4 +- app/services/system/teams-service.js | 4 +- app/services/system/user-accounts-service.js | 4 +- app/tests/middleware/error-handler.spec.js | 97 ++++++++++++++++++++ 6 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 app/tests/middleware/error-handler.spec.js diff --git a/app/exceptions/index.js b/app/exceptions/index.js index 4928d282..4ad32b55 100644 --- a/app/exceptions/index.js +++ b/app/exceptions/index.js @@ -1,5 +1,35 @@ 'use strict'; +function isErrorOptions(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function normalizeErrorOptions(options) { + if (options instanceof Error) { + const normalized = {}; + + if (options.message) { + normalized.details = options.message; + } + + normalized.cause = options; + + for (const key of Object.keys(options)) { + if (!(key in normalized)) { + normalized[key] = options[key]; + } + } + + return normalized; + } + + if (isErrorOptions(options)) { + return options; + } + + return null; +} + class CustomError extends Error { constructor(message, options = {}) { super(message); @@ -8,9 +38,21 @@ class CustomError extends Error { this.name = this.constructor.name; // Apply options (if defined) to the error object - for (const key in options) { - if (Object.prototype.hasOwnProperty.call(options, key)) { - this[key] = options[key]; + const normalizedOptions = normalizeErrorOptions(options); + if (normalizedOptions) { + if (normalizedOptions.cause instanceof Error) { + Object.defineProperty(this, 'cause', { + value: normalizedOptions.cause, + enumerable: false, + writable: true, + configurable: true, + }); + } + + for (const key in normalizedOptions) { + if (key !== 'cause' && Object.prototype.hasOwnProperty.call(normalizedOptions, key)) { + this[key] = normalizedOptions[key]; + } } } } @@ -29,8 +71,13 @@ class BadlyFormattedParameterError extends CustomError { } class DuplicateIdError extends CustomError { - constructor(options) { - super('Duplicate id', options); + constructor(messageOrOptions, options) { + if (typeof messageOrOptions === 'string') { + super(messageOrOptions, options); + return; + } + + super('Duplicate id', messageOrOptions); } } @@ -167,8 +214,13 @@ class AnonymousUserAccountNotFoundError extends CustomError { } class InvalidTypeError extends CustomError { - constructor(options) { - super('Invalid stix.type', options); + constructor(messageOrOptions, options) { + if (typeof messageOrOptions === 'string') { + super(messageOrOptions, options); + return; + } + + super('Invalid stix.type', messageOrOptions); } } @@ -179,8 +231,13 @@ class ImmutablePropertyError extends CustomError { } class InvalidPostOperationError extends CustomError { - constructor(options) { - super('Cannot set the following keys:', options); + constructor(messageOrOptions, options) { + if (typeof messageOrOptions === 'string') { + super(messageOrOptions, options); + return; + } + + super('Cannot set the following keys:', messageOrOptions); } } diff --git a/app/repository/user-accounts-repository.js b/app/repository/user-accounts-repository.js index b6c58479..447acbc5 100644 --- a/app/repository/user-accounts-repository.js +++ b/app/repository/user-accounts-repository.js @@ -171,7 +171,7 @@ class UserAccountsRepository { } } catch (err) { if (err.name === 'CastError') { - throw new BadlyFormattedParameterError('userId'); + throw new BadlyFormattedParameterError({ parameterName: 'userId' }); } else { throw err; } diff --git a/app/services/stix/collections-service.js b/app/services/stix/collections-service.js index 370bfe0d..4551d14f 100644 --- a/app/services/stix/collections-service.js +++ b/app/services/stix/collections-service.js @@ -263,7 +263,7 @@ class CollectionsService extends BaseService { const collections = await this.repository.retrieveOneByIdLean(stixId); if (!collections) { - throw new BadlyFormattedParameterError('stixId'); + throw new BadlyFormattedParameterError({ parameterName: 'stixId' }); } if (deleteAllContents) { @@ -287,7 +287,7 @@ class CollectionsService extends BaseService { const collection = await this.repository.retrieveOneByVersionLean(stixId, modified); if (!collection) { - throw new BadlyFormattedParameterError('stixId'); + throw new BadlyFormattedParameterError({ parameterName: 'stixId' }); } if (deleteAllContents) { diff --git a/app/services/system/teams-service.js b/app/services/system/teams-service.js index 4fcc6d02..c9909606 100644 --- a/app/services/system/teams-service.js +++ b/app/services/system/teams-service.js @@ -166,14 +166,14 @@ class TeamsService { } } catch (err) { if (err.name === 'CastError') { - throw new BadlyFormattedParameterError('teamId'); + throw new BadlyFormattedParameterError({ parameterName: 'teamId' }); } else { throw err; } } } catch (err) { if (err.name === 'CastError') { - throw new BadlyFormattedParameterError('teamId'); + throw new BadlyFormattedParameterError({ parameterName: 'teamId' }); } else { throw err; } diff --git a/app/services/system/user-accounts-service.js b/app/services/system/user-accounts-service.js index 4c198dcb..e42f0b70 100644 --- a/app/services/system/user-accounts-service.js +++ b/app/services/system/user-accounts-service.js @@ -82,7 +82,7 @@ class UserAccountsService { return userAccount; } catch (err) { if (err.name === 'CastError') { - throw new Exceptions.BadlyFormattedParameterError('userId'); + throw new Exceptions.BadlyFormattedParameterError({ parameterName: 'userId' }); } else { throw err; } @@ -101,7 +101,7 @@ class UserAccountsService { return userAccount; } catch (err) { if (err.name === 'CastError') { - throw new Exceptions.BadlyFormattedParameterError('email'); + throw new Exceptions.BadlyFormattedParameterError({ parameterName: 'email' }); } else { throw err; } diff --git a/app/tests/middleware/error-handler.spec.js b/app/tests/middleware/error-handler.spec.js new file mode 100644 index 00000000..f84f85ae --- /dev/null +++ b/app/tests/middleware/error-handler.spec.js @@ -0,0 +1,97 @@ +'use strict'; + +const { expect } = require('expect'); +const sinon = require('sinon'); + +const logger = require('../../lib/logger'); +const errorHandler = require('../../lib/error-handler'); +const { DatabaseError, DuplicateIdError, InvalidPostOperationError } = require('../../exceptions'); + +describe('error-handler middleware', function () { + beforeEach(function () { + sinon.stub(logger, 'warn'); + sinon.stub(logger, 'error'); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('should not serialize string characters from InvalidPostOperationError', function () { + const err = new InvalidPostOperationError( + 'Subtechniques require a parentTechniqueId query parameter. Provide the parent technique ATT&CK ID (e.g., T1234).', + ); + const res = { + status: sinon.stub().returnsThis(), + send: sinon.stub().returnsThis(), + }; + const next = sinon.stub(); + + errorHandler.serviceExceptions(err, {}, res, next); + + expect(Object.keys(err).filter((key) => /^\d+$/.test(key))).toEqual([]); + expect(res.status.calledOnceWithExactly(400)).toBe(true); + expect(res.send.calledOnceWithExactly(err.message)).toBe(true); + expect(next.called).toBe(false); + }); + + it('should preserve structured error properties for InvalidPostOperationError', function () { + const err = new InvalidPostOperationError({ + details: ['created', 'modified'], + }); + const res = { + status: sinon.stub().returnsThis(), + send: sinon.stub().returnsThis(), + }; + const next = sinon.stub(); + + errorHandler.serviceExceptions(err, {}, res, next); + + expect(res.status.calledOnceWithExactly(400)).toBe(true); + expect( + res.send.calledOnceWithExactly({ + message: 'Cannot set the following keys:', + details: ['created', 'modified'], + }), + ).toBe(true); + expect(next.called).toBe(false); + }); + + it('should preserve custom messages for DuplicateIdError', function () { + const err = new DuplicateIdError('ATT&CK ID T1234 is already in use'); + const res = { + status: sinon.stub().returnsThis(), + send: sinon.stub().returnsThis(), + }; + const next = sinon.stub(); + + errorHandler.serviceExceptions(err, {}, res, next); + + expect(res.status.calledOnceWithExactly(409)).toBe(true); + expect(res.send.calledOnceWithExactly('ATT&CK ID T1234 is already in use')).toBe(true); + expect(next.called).toBe(false); + }); + + it('should preserve wrapped error details for DatabaseError', function () { + const err = new DatabaseError(new Error('Mongo connection failed')); + const res = { + status: sinon.stub().returnsThis(), + send: sinon.stub().returnsThis(), + }; + const next = sinon.stub(); + + errorHandler.serviceExceptions(err, {}, res, next); + + expect(res.status.calledOnceWithExactly(500)).toBe(true); + expect( + res.send.calledOnceWithExactly({ + message: 'The database operation failed.', + details: 'Mongo connection failed', + }), + ).toBe(true); + expect(err.cause).toBeInstanceOf(Error); + expect(err.cause.message).toBe('Mongo connection failed'); + expect(Object.keys(err)).not.toContain('cause'); + expect(next.called).toBe(false); + }); +}); From 21f36f2ec014804f5524b3e46b46b6e25b0f03c9 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:16:16 -0400 Subject: [PATCH 304/370] fix: drop required=true from the campaign mongoose schema for custom citation fields Instead let the ADM be the sole source of validation truth. --- app/models/campaign-model.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/campaign-model.js b/app/models/campaign-model.js index e4212799..2112f749 100644 --- a/app/models/campaign-model.js +++ b/app/models/campaign-model.js @@ -11,12 +11,12 @@ const stixCampaign = { name: { type: String, required: true }, description: String, aliases: { type: [String], default: undefined }, - first_seen: { type: Date, required: true }, - last_seen: { type: Date, required: true }, + first_seen: Date, + last_seen: Date, // ATT&CK custom stix properties - x_mitre_first_seen_citation: { type: String, required: true }, - x_mitre_last_seen_citation: { type: String, required: true }, + x_mitre_first_seen_citation: String, + x_mitre_last_seen_citation: String, x_mitre_modified_by_ref: String, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_version: String, From 4085d7419b70df0b1b67950df21abeda5edf348a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:38:25 -0400 Subject: [PATCH 305/370] fix: stop throwing exception when parentTechniqueId query param is omitted This workflow has not been implemented on the backend yet. Log a warning instead. --- app/lib/attack-id-generator.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/lib/attack-id-generator.js b/app/lib/attack-id-generator.js index cc151b7c..4a77c1cf 100644 --- a/app/lib/attack-id-generator.js +++ b/app/lib/attack-id-generator.js @@ -164,7 +164,9 @@ async function generateAttackId( throw new Error('Subtechniques are only valid for attack-pattern STIX type'); } if (!parentTechniqueAttackId) { - throw new Error('Parent technique ATT&CK ID is required for subtechnique generation'); + const errorMessage = 'Parent technique ATT&CK ID is required for subtechnique generation'; + logger.warn(errorMessage); + // throw new Error(errorMessage); // TODO reenable after migrating workflow to BE } // Validate parent ID format (must be T#### or PREFIX-T####) From c8274ac9c95c5ad2c5f1d7ddfb6643277dde025d Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:57:15 -0400 Subject: [PATCH 306/370] fix(external-references): correct detection strategy website url --- app/lib/external-reference-builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/external-reference-builder.js b/app/lib/external-reference-builder.js index e8fee40c..f6d193d8 100644 --- a/app/lib/external-reference-builder.js +++ b/app/lib/external-reference-builder.js @@ -14,7 +14,7 @@ const STIX_TYPE_TO_URL_PATH = { campaign: 'campaigns', 'x-mitre-data-source': 'datasources', 'x-mitre-data-component': 'datacomponents', - 'x-mitre-detection-strategy': 'detection-strategies', + 'x-mitre-detection-strategy': 'detectionstrategies', 'x-mitre-analytic': 'analytics', 'x-mitre-asset': 'assets', 'x-mitre-tactic': 'tactics', From 875318c977e5e78573bb960c340758954afba113 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:05:22 -0400 Subject: [PATCH 307/370] fix(analytics): format AN IDs as Analytic --- app/services/stix/analytics-service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index 7f7245aa..e87b9366 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -218,7 +218,8 @@ class AnalyticsService extends BaseService { // eslint-disable-next-line no-unused-vars async beforeCreate(data, options) { // Analytic name matches its ATT&CK ID - data.stix.name = data.workspace.attack_id; + const id = data.workspace.attack_id; + data.stix.name = id.replace(/^AN(\d+)$/, 'Analytic $1'); logger.debug(`Setting name to match ATT&CK ID: ${data.stix.name}`); // Initialize embedded_relationships if not present From 88ee68aaf712b0506b7a913e7bfa6b2bd69d76ce Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:10:22 -0400 Subject: [PATCH 308/370] fix(analytics): fallback x_mitre_log_source_references + x_mitre_mutable_elements to undefined These were defaulting to undefined, resulting in FE subsequent POSTs triggering ADM validation errors due to the no-empty-list STIX rule. Also updated the remove-empty-array-fields migration script to strip preexisting documents of any such empty arrays. --- app/models/analytic-model.js | 4 ++-- ...-fields.js => 20260424210128-remove-empty-array-fields.js} | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) rename migrations/{20260409141046-remove-empty-array-fields.js => 20260424210128-remove-empty-array-fields.js} (97%) diff --git a/app/models/analytic-model.js b/app/models/analytic-model.js index 70da2cdc..5f515b57 100644 --- a/app/models/analytic-model.js +++ b/app/models/analytic-model.js @@ -31,8 +31,8 @@ const stixAnalytic = { x_mitre_attack_spec_version: String, x_mitre_domains: { type: [String], default: undefined }, x_mitre_platforms: { type: [String], default: undefined }, - x_mitre_log_source_references: [logSourceReferenceSchema], - x_mitre_mutable_elements: [mutableElementSchema], + x_mitre_log_source_references: { type: [logSourceReferenceSchema], default: undefined }, + x_mitre_mutable_elements: { type: [mutableElementSchema], default: undefined }, }; // Create the definition diff --git a/migrations/20260409141046-remove-empty-array-fields.js b/migrations/20260424210128-remove-empty-array-fields.js similarity index 97% rename from migrations/20260409141046-remove-empty-array-fields.js rename to migrations/20260424210128-remove-empty-array-fields.js index 71969f5a..3325b173 100644 --- a/migrations/20260409141046-remove-empty-array-fields.js +++ b/migrations/20260424210128-remove-empty-array-fields.js @@ -23,6 +23,8 @@ const fields = [ 'stix.x_mitre_aliases', 'stix.x_mitre_analytic_refs', 'stix.x_mitre_collection_layers', + 'stix.x_mitre_log_source_references', + 'stix.x_mitre_mutable_elements', 'stix.x_mitre_contributors', 'stix.x_mitre_domains', 'stix.x_mitre_platforms', From 9f93cf866f062b63a76b6add8641c37bf096691e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:11:15 -0400 Subject: [PATCH 309/370] fix(collections): set default value for x_mitre_contents to undefined These were defaulting to undefined, resulting in FE subsequent POSTs triggering ADM validation errors due to the no-empty-list STIX rule. Also updated the remove-empty-array-fields migration script to strip preexisting documents of any such empty arrays. --- app/models/collection-model.js | 2 +- migrations/20260424210128-remove-empty-array-fields.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/collection-model.js b/app/models/collection-model.js index 4261acee..adad500b 100644 --- a/app/models/collection-model.js +++ b/app/models/collection-model.js @@ -18,7 +18,7 @@ const xMitreCollection = { description: String, x_mitre_modified_by_ref: String, - x_mitre_contents: [xMitreContentSchema], + x_mitre_contents: { type: [xMitreContentSchema], default: undefined }, x_mitre_deprecated: { type: Boolean, required: true, default: false }, x_mitre_domains: { type: [String], default: undefined }, x_mitre_version: String, diff --git a/migrations/20260424210128-remove-empty-array-fields.js b/migrations/20260424210128-remove-empty-array-fields.js index 3325b173..d0892cb8 100644 --- a/migrations/20260424210128-remove-empty-array-fields.js +++ b/migrations/20260424210128-remove-empty-array-fields.js @@ -28,6 +28,7 @@ const fields = [ 'stix.x_mitre_contributors', 'stix.x_mitre_domains', 'stix.x_mitre_platforms', + 'stix.x_mitre_contents', 'workspace.embedded_relationships', 'workspace.collections', ]; From 42354104317814757f3c5b2330d77429f9da8e56 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:22:12 -0400 Subject: [PATCH 310/370] fix(create-mongo-views): set stix.type filter for assets to 'x-mitre-asset' --- app/lib/create-mongo-views.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/create-mongo-views.js b/app/lib/create-mongo-views.js index 54a65433..2a3b8f9a 100644 --- a/app/lib/create-mongo-views.js +++ b/app/lib/create-mongo-views.js @@ -132,7 +132,7 @@ async function createFilteredViews(db, viewType, typeToFilter) { // --------------------------------------------------------------------------- const ATTACK_TYPE_TO_MONGO_FILTER = { - assets: 'asset', + assets: 'x-mitre-asset', campaigns: 'campaign', datacomponents: 'x-mitre-data-component', datasources: 'x-mitre-data-source', From 6e80f37edd5e561d5b2fb7b56c38678add47eb4c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:43:43 -0400 Subject: [PATCH 311/370] fix(migrations): target additional array fields that could be empty Found historical evidence of the following keys being empty arrays: x_mitre_data_sources, x_mitre_effective_permissions, x_mitre_permissions_required, x_mitre_system_requirements, kill_chain_phases, x_mitre_defense_bypassed, x_mitre_tactic_type These fields will be popped/stripped from Mongo documents if they exist and are empty arrays. --- ...0260429172049-remove-empty-array-fields.js | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 migrations/20260429172049-remove-empty-array-fields.js diff --git a/migrations/20260429172049-remove-empty-array-fields.js b/migrations/20260429172049-remove-empty-array-fields.js new file mode 100644 index 00000000..f3627ca7 --- /dev/null +++ b/migrations/20260429172049-remove-empty-array-fields.js @@ -0,0 +1,81 @@ +'use strict'; + +/** + * Remove empty array values from STIX fields that previously defaulted to []. + * + * Mongoose's default behavior for `[String]` schema fields is to initialize + * them as empty arrays. The STIX 2.1 specification states that list properties + * MUST NOT be empty — they should be absent rather than present as `[]`. + * + * The corresponding model schemas have been updated to `{ type: [String], default: undefined }` + * so new documents will omit these fields when not populated. This migration + * cleans up existing documents that already have empty arrays stored. + */ + +// Every array field that should be omitted rather than stored as []. +const fields = [ + 'stix.external_references', + 'stix.object_marking_refs', + 'stix.aliases', + 'stix.roles', + 'stix.sectors', + 'stix.tactic_refs', + 'stix.x_mitre_aliases', + 'stix.x_mitre_analytic_refs', + 'stix.x_mitre_collection_layers', + 'stix.x_mitre_log_source_references', + 'stix.x_mitre_mutable_elements', + 'stix.x_mitre_contributors', + 'stix.x_mitre_domains', + 'stix.x_mitre_platforms', + 'stix.x_mitre_data_sources', + 'stix.x_mitre_effective_permissions', + 'stix.x_mitre_permissions_required', + 'stix.x_mitre_system_requirements', + 'stix.kill_chain_phases', + 'stix.x_mitre_defense_bypassed', + 'stix.x_mitre_tactic_type', + 'stix.x_mitre_contents', + 'workspace.embedded_relationships', + 'workspace.collections', +]; + +const collectionNames = ['attackObjects', 'relationships']; + +module.exports = { + async up(db) { + let totalModified = 0; + + // Unset each field individually so we only remove fields that are actually + // empty arrays — not populated fields on the same document. + for (const collectionName of collectionNames) { + const collection = db.collection(collectionName); + for (const field of fields) { + const result = await collection.updateMany( + { [field]: { $eq: [] } }, + { $unset: { [field]: '' } }, + ); + if (result.modifiedCount > 0) { + console.log( + `Removed empty ${field} from ${result.modifiedCount} ${collectionName} document(s)`, + ); + totalModified += result.modifiedCount; + } + } + } + + console.log(`Total documents updated: ${totalModified}`); + }, + + async down(db) { + // Restore empty arrays on documents that are missing these fields. + // This is a best-effort reverse — it cannot distinguish between fields that + // were never set vs fields that were unset by the up() migration. + for (const collectionName of collectionNames) { + const collection = db.collection(collectionName); + for (const field of fields) { + await collection.updateMany({ [field]: { $exists: false } }, { $set: { [field]: [] } }); + } + } + }, +}; From 3dd24a47dd636579c51b81c5043831d8d9286e94 Mon Sep 17 00:00:00 2001 From: Scot Lunsford Date: Wed, 29 Apr 2026 15:08:08 -0500 Subject: [PATCH 312/370] feat(data-components): support domain filtering Pass the domain query parameter through the data components retrieve-all controller. Document the domain parameter in the OpenAPI path definition. Add endpoint coverage for matching, repeated, and non-matching domain filters. --- .../paths/data-components-paths.yml | 12 +++++ app/controllers/data-components-controller.js | 1 + .../data-components/data-components.spec.js | 44 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/app/api/definitions/paths/data-components-paths.yml b/app/api/definitions/paths/data-components-paths.yml index 2c8fa0b4..74dbeb04 100644 --- a/app/api/definitions/paths/data-components-paths.yml +++ b/app/api/definitions/paths/data-components-paths.yml @@ -61,6 +61,18 @@ paths: type: string allowReserved: true example: 'windows' + - name: domain + in: query + description: | + Only return data components in the given domain. + This parameter may be set multiple times to retrieve data components in any of the provided domains. + schema: + oneOf: + - type: string + - type: array + items: + type: string + example: 'enterprise-attack' - name: lastUpdatedBy in: query description: | diff --git a/app/controllers/data-components-controller.js b/app/controllers/data-components-controller.js index 98c880d0..f2401aa3 100644 --- a/app/controllers/data-components-controller.js +++ b/app/controllers/data-components-controller.js @@ -11,6 +11,7 @@ exports.retrieveAll = async function (req, res, next) { includeRevoked: req.query.includeRevoked, includeDeprecated: req.query.includeDeprecated, search: req.query.search, + domain: req.query.domain, lastUpdatedBy: req.query.lastUpdatedBy, includePagination: req.query.includePagination, }; diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index 2203a43b..dc8f77db 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -134,6 +134,50 @@ describe('Data Components API', function () { expect(dataComponent.length).toBe(1); }); + it('GET /api/data-components should return data components containing the domain', async function () { + const res = await request(app) + .get('/api/data-components?domain=enterprise-attack') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const dataComponents = res.body; + expect(dataComponents).toBeDefined(); + expect(Array.isArray(dataComponents)).toBe(true); + expect(dataComponents.length).toBe(1); + expect(dataComponents[0].stix.x_mitre_domains).toContain('enterprise-attack'); + }); + + it('GET /api/data-components should return data components containing any provided domain', async function () { + const res = await request(app) + .get('/api/data-components?domain=enterprise-attack&domain=mobile-attack') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const dataComponents = res.body; + expect(dataComponents).toBeDefined(); + expect(Array.isArray(dataComponents)).toBe(true); + expect(dataComponents.length).toBe(1); + expect(dataComponents[0].stix.x_mitre_domains).toContain('enterprise-attack'); + }); + + it('GET /api/data-components should not return any data components when searching for a non-existent domain', async function () { + const res = await request(app) + .get('/api/data-components?domain=not-a-domain') + .set('Accept', 'application/json') + .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .expect(200) + .expect('Content-Type', /json/); + + const dataComponents = res.body; + expect(dataComponents).toBeDefined(); + expect(Array.isArray(dataComponents)).toBe(true); + expect(dataComponents.length).toBe(0); + }); + it('GET /api/data-components/:id/channels returns the log source channels of the added data component', async function () { const res = await request(app) .get('/api/data-components/' + dataComponent1.stix.id + '/channels') From 7f5f19095cf22afaff6eb7dc8645447d7fa8d04c Mon Sep 17 00:00:00 2001 From: Scot Lunsford Date: Mon, 4 May 2026 09:57:49 -0500 Subject: [PATCH 313/370] fix(data-components): use dynamic test cookie name --- app/tests/api/data-components/data-components.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index dc8f77db..520222b6 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -138,7 +138,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components?domain=enterprise-attack') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -153,7 +153,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components?domain=enterprise-attack&domain=mobile-attack') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); @@ -168,7 +168,7 @@ describe('Data Components API', function () { const res = await request(app) .get('/api/data-components?domain=not-a-domain') .set('Accept', 'application/json') - .set('Cookie', `${login.passportCookieName}=${passportCookie.value}`) + .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) .expect('Content-Type', /json/); From ed85bf1c604093d250c0a25fe1db6ce4cb0efdaf Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 6 May 2026 12:15:14 -0400 Subject: [PATCH 314/370] fix(validation): upgrade ADM to v4.10.1 to treat x_mitre_contributors as optional --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f6d57fc..daf97c0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.10.0", + "@mitre-attack/attack-data-model": "^4.10.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -878,9 +878,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.10.0.tgz", - "integrity": "sha512-whLa1X8GURoNvKgpbAqZe2EymL9I5/ncbdRSDKgfYeYkt0iu3FwfzKVIlcq2sn5gKZ68o94kxSM5TDsi+2r6PA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.10.1.tgz", + "integrity": "sha512-6LqYuG44HFLEX0JT1+KJLImHNERwM+lndgBKOTi35gD5+8ZNH0dWJqN3YAYMYvcbJ3QFCMyTcWupSE8z34lH7A==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index 242a4218..a7e5911e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.10.0", + "@mitre-attack/attack-data-model": "^4.10.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", From 042ca9e901b6f68bb2a5e392d0df4a77828990bc Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 6 May 2026 12:16:46 -0400 Subject: [PATCH 315/370] fix(validation): treat workspace.validation as server-controlled Strip workspace.validation from incoming data in stripServerControlledFields and at the entry of _createFromImport, so a stale entry from a prior GET cannot ride along into the saved document. The field is now exclusively written by the scheduler and the import fail-open path. --- app/services/meta-classes/base.service.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 0d35a50f..a1b9be6c 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -355,6 +355,12 @@ class BaseService extends ServiceWithHooks { delete stix[field]; } + // Strip workspace.validation — server-controlled; recomputed on every + // create/update so a stale entry from a prior GET cannot ride along. + if (data.workspace) { + delete data.workspace.validation; + } + if (!options.preserveAttackId) { // Strip workspace.attack_id — server generates/carries forward if (data.workspace) { @@ -695,6 +701,12 @@ class BaseService extends ServiceWithHooks { * @private */ async _createFromImport(data, options) { + // Strip workspace.validation — server-controlled; the fail-open block + // below is the only legitimate writer of this field on the import path. + if (data.workspace) { + delete data.workspace.validation; + } + // Extract ATT&CK ID from external_references and propagate to workspace.attack_id const attackIdInExternalReferences = attackIdGenerator.extractAttackIdFromExternalReferences( data.stix, From bac4eb5ca5b32ae55e28f14fd5bd6600d5cd6cb8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 6 May 2026 14:06:07 -0400 Subject: [PATCH 316/370] docs(developer): explain stateful validation tracking --- docs/developer/workspace-validation.md | 202 +++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 docs/developer/workspace-validation.md diff --git a/docs/developer/workspace-validation.md b/docs/developer/workspace-validation.md new file mode 100644 index 00000000..6abd378d --- /dev/null +++ b/docs/developer/workspace-validation.md @@ -0,0 +1,202 @@ +# Stateful Validation Tracking (`workspace.validation`) + +## Overview + +Every Mongoose document in the Workbench REST API has an optional +`workspace.validation` subdocument that records the result of validating +the document's `stix` payload against the [ATT&CK Data Model +(ADM)](https://github.com/mitre-attack/attack-data-model) schemas. + +The field is **server-controlled** and **diagnostic**: it does not +gate reads, but it tells operators and client UIs which documents are +known to fail current ADM validation and why. It exists so that a long- +lived database can carry forward documents that were valid under an +older ADM version (or that pre-date validation entirely) without losing +visibility into their non-compliance. + +This document defines the field, its invariants, and the pipelines that +write or clear it. + +## Why state-track validation at all? + +ADM validation is the gate at the write boundary: every POST and PUT +runs the composed STIX object through the ADM schemas before +persistence (see [`base.service.js`](../../app/services/meta-classes/base.service.js) +pipeline stage 5, "VALIDATE WITH ADM"). If validation fails on a write, +the request throws and nothing is persisted. + +Given that gate, **a freshly-seeded database should never contain +validation errors.** State-tracking is only meaningful for documents +that bypassed the gate or were validated under different rules: + +1. **Legacy content** — documents that existed before ADM-based + validation was introduced into the request pipeline. These + documents may have shapes that current schemas reject and were + never gated on entry. + +2. **Version-skewed content** — documents written under one ADM + version that subsequently became non-compliant when Workbench + upgraded to a later ADM version. For example, content authored + under ADM v1.0 may fail ADM v2.0 validation if v2.0 tightened a + constraint or renamed a required field. + + Schema-changing Workbench upgrades are expected to ship database + migration scripts that bring existing content into compliance, so + this case should be rare in practice. It remains technically + possible whenever an upgrade lands without a corresponding migration, + or when a migration cannot fully repair a document. + +3. **Imported content** — STIX bundle imports use a fail-open path + (see below). When an imported object fails validation but the + import is allowed to proceed, the errors are recorded on the + document so they are visible after the import completes. + +## Field shape + +```jsonc +{ + "workspace": { + "validation": { + "errors": [ + { "message": "stix.x_mitre_domains is Required", + "path": ["x_mitre_domains"], + "code": "invalid_type" } + ], + "attack_spec_version": "3.3.0", + "adm_version": "1.4.2", + "validated_at": "2026-05-06T12:00:00.000Z" + } + } +} +``` + +| Field | Meaning | +|---|---| +| `errors` | Array of `{ message, path, code }` derived from Zod issues, after bypass rules are applied. | +| `attack_spec_version` | ATT&CK spec version under which validation was performed. | +| `adm_version` | NPM version of `@mitre-attack/attack-data-model` at validation time. | +| `validated_at` | UTC timestamp of the validation run. | + +The presence of the `validation` subdocument means "this document had +unresolved errors as of `validated_at`." Its **absence** means "this +document was either never validated or last passed validation." + +## Invariants + +1. `workspace.validation` is **server-controlled.** Clients cannot + set, modify, or carry forward this field through any write path. +2. The field is **recomputed (or omitted) on every successful write.** + A POST or PUT that passes ADM validation produces a document with + no `workspace.validation`. A POST or PUT that fails ADM validation + throws — nothing is persisted, and the prior document (if any) is + untouched until a future write or scheduler tick revisits it. +3. The only paths that may legitimately *set* `workspace.validation` + are the scheduler and the import fail-open path. All other paths + either clear it or leave it absent. + +## Writers and behavior + +### 1. `BaseService.create()` — POST a new (version of an) object + +[`base.service.js`](../../app/services/meta-classes/base.service.js) + +- `stripServerControlledFields()` removes any client-supplied + `workspace.validation` at the top of the pipeline. +- ADM validation runs. +- If validation fails, the request throws — nothing persists. +- If validation passes, the new document is saved with no + `workspace.validation`. + +### 2. `BaseService.updateFull()` — PUT an existing version + +- `stripServerControlledFields()` removes any client-supplied + `workspace.validation`. +- ADM validation runs against the composed object. +- If validation fails, the request throws — the existing document is + untouched. +- If validation passes: + - The composed document is merged onto the existing one. + - If the existing document had `workspace.validation`, the merge + sets it to `undefined` and a follow-up `repository.unsetField` + call removes the field from the persisted document. + +### 3. `BaseService._createFromImport()` — STIX bundle import + +This path is intentionally **fail-open**: import is the primary way +that legacy and version-skewed content enters the system, so blocking +on every validation error would make migrations impossible. + +- Any client-supplied `workspace.validation` is stripped at entry. +- Revoked or deprecated objects skip validation entirely and are + persisted with no `workspace.validation`. +- Otherwise, ADM validation runs. +- If validation fails and `options.validateContents` is set, the + import throws. +- If validation fails and `validateContents` is not set, the errors + are recorded on `workspace.validation` (with current ADM and spec + versions) and the document persists. **This is the only legitimate + client-facing setter.** +- If validation passes, the document persists with no + `workspace.validation`. + +### 4. The `validate-objects` scheduler task + +[`app/scheduler/validate-objects-task.js`](../../app/scheduler/validate-objects-task.js) + +The scheduler exists to combat **concept drift**: a document that +passed validation last week may fail today if Workbench has since +upgraded ADM. On its configured cron schedule, the task iterates every +SDO and SRO in the database, re-runs validation, and brings each +document's `workspace.validation` field back in sync with the current +ADM rules. + +For each document: + +- Revoked/deprecated objects are skipped (validation is not + meaningful for retired content). +- Validation runs and bypass rules are applied. +- If the document passes, any existing `workspace.validation` is + unset (`totalCleared`). +- If the document fails, `workspace.validation` is set to the current + errors with current ADM/spec versions (`totalErrored`). + +Because the scheduler is the only writer that can transition a +document from "valid" to "has-validation-errors" without a user +write, it is also the only mechanism that surfaces version-skewed +documents after a Workbench upgrade. + +## Lifecycle summary + +| Path | Validation outcome | `workspace.validation` after the write | +|---|---|---| +| `create()` | passes | absent | +| `create()` | fails | request throws; nothing persisted | +| `updateFull()` | passes | absent (cleared if previously present) | +| `updateFull()` | fails | request throws; existing doc untouched | +| `_createFromImport()` | passes | absent | +| `_createFromImport()` | fails + `validateContents` | request throws | +| `_createFromImport()` | fails + fail-open | server-set with current ADM/spec | +| `_createFromImport()` | revoked/deprecated | absent | +| Scheduler | passes | absent (cleared if previously present) | +| Scheduler | fails | server-set with current ADM/spec | + +## Bypass rules + +Both the request pipeline and the scheduler consult +`validation-bypasses-repository` to filter Zod issues that match a +stored bypass rule (matching on `stixType`, `errorCode`, and +`fieldPath`). A bypassed error is removed from the `errors` array +before the field is written. This allows operators to suppress known- +benign validation noise without modifying ADM itself. + +The set of bypass rules is shared between the synchronous write path +and the scheduler so that a document's validation status is consistent +regardless of which writer last touched it. + +## Reading `workspace.validation` + +There is no public endpoint that filters on this field today. Clients +that need to surface "documents needing attention" should retrieve +the relevant collection and check for the presence of +`workspace.validation` on each document. The field is a diagnostic +hint, not a workflow gate — read paths do not consult it. From a236c8583d4450369b66ef972cce40dd0c822ff6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 6 May 2026 14:17:23 -0400 Subject: [PATCH 317/370] feat(validation): bump ADM to v4.11.0 and expose ADM_LOG_LEVEL Upgrade @mitre-attack/attack-data-model to v4.11.0, which introduces a customizable internal logger. Adds the ADM_LOG_LEVEL environment variable so operators can silence the per-relationship deprecation warnings that the scheduled validate-objects task previously emitted to stdout on every run. Documents ADM_LOG_LEVEL alongside the existing VALIDATE_WITH_ADM_SCHEMAS and VALIDATE_OBJECTS_CRON knobs in template.env and a new "Validation" section of docs/admin/configuration.md. --- docs/admin/configuration.md | 54 +++++++++++++++++++++++++++++++++++++ package-lock.json | 8 +++--- package.json | 2 +- template.env | 20 ++++++++++++++ 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/docs/admin/configuration.md b/docs/admin/configuration.md index 5b04cf49..b37a9caf 100644 --- a/docs/admin/configuration.md +++ b/docs/admin/configuration.md @@ -26,6 +26,7 @@ JSON configuration files, or a combination of both. - [Basic API Key](#basic-api-key) - [Multiple Service Authentication Methods](#multiple-service-authentication-methods) - [Scheduler](#scheduler) + - [Validation](#validation) - [Collection Indexes](#collection-indexes) - [Configuration Files](#configuration-files) - [ATT\&CK Specific](#attck-specific) @@ -532,6 +533,59 @@ ENABLE_SCHEDULER=true CHECK_WORKBENCH_INTERVAL=30 ``` +### Validation + +Configuration for ATT&CK Data Model (ADM) request validation and the scheduled re-validation task. + +| Option | Environment Variable | JSON Path | Type | Default | Description | +|-------------------|-----------------------------|----------------------------------------|---------|-------------|----------------------------------------------------------------------------------------------| +| Validate Requests | `VALIDATE_WITH_ADM_SCHEMAS` | `validateRequests.withAttackDataModel` | boolean | `false` | Run incoming POST/PUT bodies through the ADM schemas before persisting | +| Re-validate Cron | `VALIDATE_OBJECTS_CRON` | `scheduler.validateObjectsCron` | string | `0 3 * * *` | Cron pattern for the background task that refreshes `workspace.validation` on every document | +| ADM Log Level | `ADM_LOG_LEVEL` | *(env only)* | enum | `warn` | Verbosity of the ADM library's internal logger | + +**`VALIDATE_WITH_ADM_SCHEMAS`** + +When enabled, every POST and PUT validates the composed STIX payload against the ADM schemas before saving. Failed validations cause the request to throw with an HTTP error and the document is not persisted. When disabled, the write pipeline does not gate on ADM compliance and downstream tools are responsible for detecting non-compliant content. + +This setting does not affect the legacy OpenAPI request validation (`VALIDATE_WITH_LEGACY_SCHEMAS`), which can be enabled or disabled independently. + +**`VALIDATE_OBJECTS_CRON`** + +The Workbench scheduler periodically re-validates every SDO and SRO in the database against the current ADM and refreshes each document's `workspace.validation` field. This combats *concept drift*: documents that passed validation under an older ADM version may become non-compliant after a Workbench upgrade. + +The cron pattern follows standard 5-field syntax (`minute hour day-of-month month day-of-week`). The default `0 3 * * *` runs the task daily at 3:00 AM. The task is skipped entirely if the global scheduler is disabled (`ENABLE_SCHEDULER=false`). + +For the lifecycle of `workspace.validation` itself, see [Stateful Validation Tracking](../developer/workspace-validation.md). + +**`ADM_LOG_LEVEL`** + +Read by the `@mitre-attack/attack-data-model` library directly — *not* a Convict-managed setting and not configurable via `JSON_CONFIG_PATH`. It controls the verbosity of the ADM library's own logger, which is independent of the Workbench `LOG_LEVEL`. + +This was introduced primarily to suppress the deprecation warning that the ADM emits for every relationship in the database during a re-validation run. Setting `ADM_LOG_LEVEL=error` (or `silent`) keeps the scheduled task quiet without affecting Workbench's own logs. + +| Level | Description | +|----------|--------------------------------------------------------------------| +| `debug` | Verbose diagnostic output | +| `info` | Informational status messages (data retrieval, parse counts, etc.) | +| `warn` | Validation issues in `relaxed` mode and deprecation warnings | +| `error` | Errors only | +| `silent` | Disables all output | + +Levels are inclusive: setting `info` enables `info`, `warn`, and `error`. The default is `warn`. + +**Examples:** + +```bash +# Enable ADM-based request validation +VALIDATE_WITH_ADM_SCHEMAS=true + +# Run re-validation hourly instead of daily at 3 AM +VALIDATE_OBJECTS_CRON=0 * * * * + +# Suppress noisy ADM deprecation warnings during scheduler runs +ADM_LOG_LEVEL=error +``` + ### Collection Indexes Configuration for ATT&CK collection index subscriptions. diff --git a/package-lock.json b/package-lock.json index daf97c0d..9a427560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.10.1", + "@mitre-attack/attack-data-model": "^4.11.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -878,9 +878,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.10.1.tgz", - "integrity": "sha512-6LqYuG44HFLEX0JT1+KJLImHNERwM+lndgBKOTi35gD5+8ZNH0dWJqN3YAYMYvcbJ3QFCMyTcWupSE8z34lH7A==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.0.tgz", + "integrity": "sha512-U9CsivFwg6EVeM4edjNQYm6LJXbQbNzIcvixjRSdp5qOCFRi2UwL1CVa2QwE9ZXuNN19ZOH9eEdQcmmL+uowOA==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index a7e5911e..22f29198 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.10.1", + "@mitre-attack/attack-data-model": "^4.11.0", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", diff --git a/template.env b/template.env index 5d23e5a7..bc5c8f75 100644 --- a/template.env +++ b/template.env @@ -71,6 +71,26 @@ DATABASE_URL= # Default: 10 #CHECK_WORKBENCH_INTERVAL=10 +# Validation +# VALIDATE_WITH_ADM_SCHEMAS (bool) - Validate POST/PUT bodies against the ATT&CK Data Model +# When true, requests that fail ADM validation are rejected before persistence. +# Default: false +#VALIDATE_WITH_ADM_SCHEMAS=false + +# VALIDATE_OBJECTS_CRON (string) - Cron pattern for the scheduled re-validation task +# Re-validates every SDO/SRO against the current ADM and refreshes workspace.validation. +# Standard 5-field cron syntax (minute hour day-of-month month day-of-week). +# Skipped entirely when ENABLE_SCHEDULER=false. +# Default: 0 3 * * * (daily at 3:00 AM) +#VALIDATE_OBJECTS_CRON=0 3 * * * + +# ADM_LOG_LEVEL (enum) - Verbosity of the @mitre-attack/attack-data-model library's logger +# Read by the ADM library directly; independent of LOG_LEVEL and not configurable via JSON. +# Options: debug, info, warn, error, silent (inclusive — info enables info+warn+error) +# Set to error or silent to suppress per-relationship deprecation warnings during scheduler runs. +# Default: warn +#ADM_LOG_LEVEL=warn + # Session # SESSION_SECRET (string) - Secret to sign session cookies. # Default: securely generated at startup (changes on restart; not recommended for production). From 2a6ac52aff24def986add93a55f6fedf906e5da6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 7 May 2026 09:44:17 -0400 Subject: [PATCH 318/370] fix(migrations): remove older duplicate migration script This migration script is superceded by a newer one. --- ...0260424210128-remove-empty-array-fields.js | 74 ------------------- 1 file changed, 74 deletions(-) delete mode 100644 migrations/20260424210128-remove-empty-array-fields.js diff --git a/migrations/20260424210128-remove-empty-array-fields.js b/migrations/20260424210128-remove-empty-array-fields.js deleted file mode 100644 index d0892cb8..00000000 --- a/migrations/20260424210128-remove-empty-array-fields.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -/** - * Remove empty array values from STIX fields that previously defaulted to []. - * - * Mongoose's default behavior for `[String]` schema fields is to initialize - * them as empty arrays. The STIX 2.1 specification states that list properties - * MUST NOT be empty — they should be absent rather than present as `[]`. - * - * The corresponding model schemas have been updated to `{ type: [String], default: undefined }` - * so new documents will omit these fields when not populated. This migration - * cleans up existing documents that already have empty arrays stored. - */ - -// Every array field that should be omitted rather than stored as []. -const fields = [ - 'stix.external_references', - 'stix.object_marking_refs', - 'stix.aliases', - 'stix.roles', - 'stix.sectors', - 'stix.tactic_refs', - 'stix.x_mitre_aliases', - 'stix.x_mitre_analytic_refs', - 'stix.x_mitre_collection_layers', - 'stix.x_mitre_log_source_references', - 'stix.x_mitre_mutable_elements', - 'stix.x_mitre_contributors', - 'stix.x_mitre_domains', - 'stix.x_mitre_platforms', - 'stix.x_mitre_contents', - 'workspace.embedded_relationships', - 'workspace.collections', -]; - -const collectionNames = ['attackObjects', 'relationships']; - -module.exports = { - async up(db) { - let totalModified = 0; - - // Unset each field individually so we only remove fields that are actually - // empty arrays — not populated fields on the same document. - for (const collectionName of collectionNames) { - const collection = db.collection(collectionName); - for (const field of fields) { - const result = await collection.updateMany( - { [field]: { $eq: [] } }, - { $unset: { [field]: '' } }, - ); - if (result.modifiedCount > 0) { - console.log( - `Removed empty ${field} from ${result.modifiedCount} ${collectionName} document(s)`, - ); - totalModified += result.modifiedCount; - } - } - } - - console.log(`Total documents updated: ${totalModified}`); - }, - - async down(db) { - // Restore empty arrays on documents that are missing these fields. - // This is a best-effort reverse — it cannot distinguish between fields that - // were never set vs fields that were unset by the up() migration. - for (const collectionName of collectionNames) { - const collection = db.collection(collectionName); - for (const field of fields) { - await collection.updateMany({ [field]: { $exists: false } }, { $set: { [field]: [] } }); - } - } - }, -}; From 7847b32852a2cbd14635c4d58ab312235d3e7534 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 7 May 2026 09:50:18 -0400 Subject: [PATCH 319/370] fix(stix-bundles): resolve issue with x_mitre_domains not appearing on campaigns x_mitre_domains was recently dropped from the Mongoose schemas as part of an effort to drop empty arrays from stateful Mongo Documents and getting Documents as close to STIX compliant as possible so as to minimize the amount of post retrieval transformations are needed. This created a bug in the STIX bundle export workflow: even though campaigns were correctly being flagged as secondary objects and consequently appended with the x_mitre_domains key, the key was getting dropped because we were operating on Mongoose Document instances rather than POJOs. If a Mongoose schema doesn't declare a field explicitly, as in the case of x_mitre_domains, Mongoose will strip the field during serialization. Conveniently, we already have repository methods for returning POJOs rather than Document instances, which is afforded by the Mongoose .lean() method. Changing the global retrieval step in the STIX bundle export workflow to the lean version resolves the issue. --- app/services/stix/stix-bundles-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/stix/stix-bundles-service.js b/app/services/stix/stix-bundles-service.js index f986b4cb..50908869 100644 --- a/app/services/stix/stix-bundles-service.js +++ b/app/services/stix/stix-bundles-service.js @@ -875,7 +875,7 @@ class StixBundlesService extends BaseService { } // Use the existing repository method that exactly matches the original logic - const attackObject = await this.repositories.attackObject.retrieveLatestByStixId(stixId); + const attackObject = await this.repositories.attackObject.retrieveLatestByStixIdLean(stixId); if (attackObject) { this.attackObjectCache.set(cacheKey, attackObject); From 8ef3aeaec8387f57c5e91a9d25b2d6a7224c629e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 7 May 2026 16:01:55 -0400 Subject: [PATCH 320/370] refactor(validation): use ADM partial schemas for WIP objects Select ADM-provided partial schemas for work-in-progress objects and full schemas for other workflow states. Also clarify the validation-schemas.js comments so the WIP-vs-full selection logic is easier to follow. --- app/lib/validation-schemas.js | 103 ++++++++++++++++------------------ 1 file changed, 48 insertions(+), 55 deletions(-) diff --git a/app/lib/validation-schemas.js b/app/lib/validation-schemas.js index 266cdb41..ede9f767 100644 --- a/app/lib/validation-schemas.js +++ b/app/lib/validation-schemas.js @@ -4,22 +4,28 @@ const { tacticSchema, /** techniques */ - techniqueBaseSchema, + techniqueSchema, + techniquePartialSchema, /** groups */ - groupBaseSchema, + groupSchema, + groupPartialSchema, /** malware */ - malwareBaseSchema, + malwareSchema, + malwarePartialSchema, /** tools */ - toolBaseSchema, + toolSchema, + toolPartialSchema, /** campaigns */ - campaignBaseSchema, + campaignSchema, + campaignPartialSchema, /** relationships */ - relationshipBaseSchema, + relationshipSchema, + relationshipPartialSchema, /** simple schemas (no checks/refinements) */ identitySchema, @@ -32,63 +38,46 @@ const { matrixSchema, collectionSchema, markingDefinitionSchema, -} = require('@mitre-attack/attack-data-model/dist'); +} = require('@mitre-attack/attack-data-model/dist/index.cjs'); -// The ADM exports bundles of refinements (checks) for any schemas which support partial schema derivatives. -// e.g., The technique.schema module exports: (1) techniqueBaseSchema, (2) techniquePartialSchema, (3) techniqueChecks +// The ADM package exposes two validation shapes for several STIX types: +// - a full schema for normal validation +// - a prebuilt partial schema for draft/work-in-progress validation // -// This enables users to easily compose custom schemas w/o running into the Zod restriction introduced in v4.3.6 where -// `.omit`, `.pick`, and `.partial` throw when `.check` is chained on. -// (Details: https://github.com/mitre-attack/attack-data-model/pull/65) +// Workbench treats `work-in-progress` objects differently from objects in +// later workflow states. WIP objects are allowed to omit fields that are still +// being authored, while `awaiting-review` and `reviewed` objects should be +// held to the complete schema. // -// In Workbench, this is specifically necessary because the ADM validation middleware needs to omit checking fields -// which only the backend sets, e.g., `x_mitre_attack_spec_version` is set by the backend, therefore it's never passed/set in -// the req.body of POST/create requests, therefore we need to avoid scrutinizing that field in the ADM validation middleware. -// -// Composition order (for schemas with checks): -// base schema → .omit() → .partial() (if WIP) → .check(checks) -// This ensures .omit() and .partial() are called BEFORE .check(), avoiding the Zod restriction. - -const { - techniqueChecks, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/technique.schema'); -const { groupChecks } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/group.schema'); -const { - campaignChecks, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/campaign.schema'); -const { - relationshipChecks, -} = require('@mitre-attack/attack-data-model/dist/schemas/sro/relationship.schema'); -const { - malwareChecks, -} = require('@mitre-attack/attack-data-model/dist/schemas/sdo/malware.schema'); -const { toolChecks } = require('@mitre-attack/attack-data-model/dist/schemas/sdo/tool.schema'); - +// We prefer the ADM-provided `*PartialSchema` exports when they exist rather +// than deriving them ourselves at call time. That keeps this layer aligned +// with however ADM composes partial validation for schemas that may include +// additional checks or refinements. const STIX_SCHEMAS = { 'x-mitre-tactic': tacticSchema, 'attack-pattern': { - base: techniqueBaseSchema, - checks: techniqueChecks, + full: techniqueSchema, + partial: techniquePartialSchema, }, 'intrusion-set': { - base: groupBaseSchema, - checks: groupChecks, + full: groupSchema, + partial: groupPartialSchema, }, malware: { - base: malwareBaseSchema, - checks: malwareChecks, + full: malwareSchema, + partial: malwarePartialSchema, }, tool: { - base: toolBaseSchema, - checks: toolChecks, + full: toolSchema, + partial: toolPartialSchema, }, campaign: { - base: campaignBaseSchema, - checks: campaignChecks, + full: campaignSchema, + partial: campaignPartialSchema, }, relationship: { - base: relationshipBaseSchema, - checks: relationshipChecks, + full: relationshipSchema, + partial: relationshipPartialSchema, }, identity: identitySchema, 'course-of-action': mitigationSchema, @@ -105,12 +94,15 @@ const STIX_SCHEMAS = { /** * Get the schema to use for validating a STIX object. * - * Some STIX types define both a "base" schema and "checks" (refinements), - * while others only define a single schema (no refinements). This helper - * composes the correct schema based on the STIX type and workflow status. + * Some STIX types define both a full schema and a prebuilt partial schema, + * while others only define a single schema (no partial variant). This helper + * selects the correct schema based on the STIX type and workflow status. * - * Composition order (for schemas with checks): - * base → .partial() (if WIP) → .check(checks) + * Determination rules: + * - `work-in-progress` uses partial validation so drafts can omit required fields + * - every other workflow state uses full validation + * - if ADM exports a dedicated partial schema, use it directly + * - otherwise, derive a partial schema locally with `.partial()` * * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") @@ -120,11 +112,12 @@ function getSchema(stixType, status) { const admSchemaRef = STIX_SCHEMAS[stixType]; if (!admSchemaRef) return null; + // Only draft objects get partial validation. Once an object leaves the + // work-in-progress state, we validate it against the full schema. const isWip = status === 'work-in-progress'; - if (admSchemaRef.base && admSchemaRef.checks) { - const base = isWip ? admSchemaRef.base.partial() : admSchemaRef.base; - return base.check(admSchemaRef.checks); + if (admSchemaRef.full && admSchemaRef.partial) { + return isWip ? admSchemaRef.partial : admSchemaRef.full; } return isWip ? admSchemaRef.partial() : admSchemaRef; From b97ae549b975690efcff8c288b56653b2e91dfe1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 7 May 2026 17:08:48 -0400 Subject: [PATCH 321/370] fix(base-service): stop logging irrelevant warning when modifying objs with known validation errors workspace.validation is now controlled by the server. Users cannot set/forge their own payloads. Thus it's irrelevant to log a warning pertaining to a field that's going to be dropped anyways. --- app/services/meta-classes/base.service.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index a1b9be6c..24b4c65a 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -611,20 +611,6 @@ class BaseService extends ServiceWithHooks { } if (existingObject) { - // Block POST if the existing object has unresolved validation issues. - // Users must fix via PUT/updateFull first. - if (existingObject.workspace?.validation?.errors?.length > 0) { - const warning = - `Object ${data.stix.id} has unresolved validation issues. ` + - `Use PUT to update the existing version and resolve the issues before creating new versions.`; - console.warn(warning); - // TODO figure out the optimal way to treat imported objects with known validation errors - // throw new ObjectHasValidationIssuesError({ - // details: warning, - // validationErrors: existingObject.workspace.validation.errors, - // }); - } - // New version of an existing object — carry forward revoked status, set modified_by data.stix.revoked = existingObject.stix.revoked ?? false; data.stix.x_mitre_modified_by_ref = organizationIdentityRef; From 48fd429887af4d3f30eba9cca50fac317ac82a80 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 7 May 2026 17:11:52 -0400 Subject: [PATCH 322/370] feat(migrations): normalize x_mitre_platforms and persist automation audit trails Align the frontend-exposed x_mitre_platforms allowed values with the ATT&CK Data Model source of truth so newly authored content uses the current canonical platform vocabulary. Add a normalization migration that rewrites legacy platform values on active latest objects by creating new object versions through the normal service-layer workflow. This preserves history while bringing existing content into compliance with the updated platform taxonomy. Introduce a reusable automation run recorder that persists run summaries and per-item outcomes in automationRuns and automationRunItems. This makes migrations and other automated repair processes observable beyond transient server logs and establishes a durable audit trail for future automation work. Document the new taxonomy and the administrative workflow for inspecting automation run records. --- app/config/allowed-values.json | 12 +- app/lib/automation-run-recorder.js | 139 ++++++ app/services/meta-classes/base.service.js | 1 + .../system-configuration.spec.js | 20 +- docs/README.md | 2 + docs/admin/automation-runs.md | 163 ++++++ docs/admin/configuration.md | 1 + docs/developer/automation-runs.md | 315 ++++++++++++ ...60507130000-normalize-x-mitre-platforms.js | 463 ++++++++++++++++++ 9 files changed, 1112 insertions(+), 4 deletions(-) create mode 100644 app/lib/automation-run-recorder.js create mode 100644 docs/admin/automation-runs.md create mode 100644 docs/developer/automation-runs.md create mode 100644 migrations/20260507130000-normalize-x-mitre-platforms.js diff --git a/app/config/allowed-values.json b/app/config/allowed-values.json index a83a1720..c899f079 100644 --- a/app/config/allowed-values.json +++ b/app/config/allowed-values.json @@ -13,10 +13,12 @@ "macOS", "Windows", "Network Devices", + "Google Workspace", "SaaS", "IaaS", "Containers", "Identity Provider", + "Azure AD", "Office Suite", "ESXi" ] @@ -30,7 +32,6 @@ "allowedValues": [ "Field Controller/RTU/PLC/IED", "Safety Instrumented System/Protection Relay", - "Device Configuration/Parameters", "Control Server", "Input/Output Server", "Data Historian", @@ -58,10 +59,12 @@ "macOS", "Windows", "Network Devices", + "Google Workspace", "SaaS", "IaaS", "Containers", "Identity Provider", + "Azure AD", "Office Suite", "ESXi" ] @@ -75,7 +78,6 @@ "allowedValues": [ "Field Controller/RTU/PLC/IED", "Safety Instrumented System/Protection Relay", - "Device Configuration/Parameters", "Control Server", "Input/Output Server", "Data Historian", @@ -186,10 +188,12 @@ "macOS", "Windows", "Network Devices", + "Google Workspace", "IaaS", "SaaS", "Containers", "Identity Provider", + "Azure AD", "Office Suite", "ESXi" ] @@ -249,6 +253,8 @@ "SaaS", "Network Devices", "Identity Provider", + "Azure AD", + "Google Workspace", "Office Suite", "ESXi" ] @@ -305,7 +311,7 @@ "domains": [ { "domainName": "ics-attack", - "allowedValues": ["Windows", "Linux", "Network", "Embedded", "Cloud"] + "allowedValues": ["Windows", "Linux", "Network Devices", "Embedded", "IaaS", "SaaS"] } ] } diff --git a/app/lib/automation-run-recorder.js b/app/lib/automation-run-recorder.js new file mode 100644 index 00000000..b5acd1ac --- /dev/null +++ b/app/lib/automation-run-recorder.js @@ -0,0 +1,139 @@ +'use strict'; + +const os = require('os'); +const { v4: uuidv4 } = require('uuid'); +const logger = require('./logger'); + +const AUTOMATION_RUN_SCHEMA_VERSION = 1; +const AUTOMATION_RUNS_COLLECTION = 'automationRuns'; +const AUTOMATION_RUN_ITEMS_COLLECTION = 'automationRunItems'; + +async function ensureIndexes(db) { + await Promise.all([ + db.collection(AUTOMATION_RUNS_COLLECTION).createIndex({ run_id: 1 }, { unique: true }), + db.collection(AUTOMATION_RUNS_COLLECTION).createIndex({ automation_type: 1, started_at: -1 }), + db.collection(AUTOMATION_RUNS_COLLECTION).createIndex({ name: 1, started_at: -1 }), + db.collection(AUTOMATION_RUN_ITEMS_COLLECTION).createIndex({ run_id: 1, sequence: 1 }), + db.collection(AUTOMATION_RUN_ITEMS_COLLECTION).createIndex({ run_id: 1, status: 1 }), + db + .collection(AUTOMATION_RUN_ITEMS_COLLECTION) + .createIndex({ 'target.stix_id': 1, recorded_at: -1 }, { sparse: true }), + ]); +} + +function serializeError(error) { + if (!error) return null; + + return { + name: error.name, + message: error.message, + stack: error.stack, + }; +} + +class AutomationRunRecorder { + constructor(db, options) { + this.db = db; + this.runId = options.runId || uuidv4(); + this.automationType = options.automationType; + this.name = options.name; + this.trigger = options.trigger || {}; + this.scope = options.scope || {}; + this.metadata = options.metadata || {}; + this.startedAt = new Date(); + this.sequence = 0; + this.runsCollection = db.collection(AUTOMATION_RUNS_COLLECTION); + this.itemsCollection = db.collection(AUTOMATION_RUN_ITEMS_COLLECTION); + } + + async start() { + await ensureIndexes(this.db); + + await this.runsCollection.insertOne({ + schema_version: AUTOMATION_RUN_SCHEMA_VERSION, + run_id: this.runId, + automation_type: this.automationType, + name: this.name, + status: 'running', + started_at: this.startedAt, + finished_at: null, + trigger: this.trigger, + scope: this.scope, + runtime: { + hostname: os.hostname(), + pid: process.pid, + node_version: process.version, + platform: process.platform, + arch: process.arch, + }, + metadata: this.metadata, + counts: {}, + warnings: {}, + verification: {}, + summary: null, + error_summary: null, + items: { + collection: AUTOMATION_RUN_ITEMS_COLLECTION, + }, + }); + + return this; + } + + async recordItem(item) { + this.sequence += 1; + + await this.itemsCollection.insertOne({ + schema_version: AUTOMATION_RUN_SCHEMA_VERSION, + run_id: this.runId, + automation_type: this.automationType, + name: this.name, + recorded_at: new Date(), + sequence: this.sequence, + ...item, + }); + } + + async finish({ status, counts, warnings, verification, summary, errorSummary }) { + await this.runsCollection.updateOne( + { run_id: this.runId }, + { + $set: { + status, + finished_at: new Date(), + counts: counts || {}, + warnings: warnings || {}, + verification: verification || {}, + summary: summary || null, + error_summary: errorSummary || null, + }, + }, + ); + } + + log(level, message, details) { + const prefix = `[${this.automationType}:${this.name}][${this.runId}]`; + const formattedMessage = details + ? `${prefix} ${message} ${JSON.stringify(details)}` + : `${prefix} ${message}`; + + if (typeof logger[level] === 'function') { + logger[level](formattedMessage); + } else { + logger.info(formattedMessage); + } + } +} + +async function createAutomationRunRecorder(db, options) { + const recorder = new AutomationRunRecorder(db, options); + return recorder.start(); +} + +module.exports = { + AUTOMATION_RUN_SCHEMA_VERSION, + AUTOMATION_RUNS_COLLECTION, + AUTOMATION_RUN_ITEMS_COLLECTION, + createAutomationRunRecorder, + serializeError, +}; diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 24b4c65a..93e86bf3 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -481,6 +481,7 @@ class BaseService extends ServiceWithHooks { * @param {string} [options.userAccountId] - The authenticated user's account ID * @param {string} [options.parentTechniqueId] - Parent technique ATT&CK ID (for subtechniques) * @param {boolean} [options.dryRun] - If true, compose and validate but skip persistence + * @param {object} [options.automationContext] - Optional automation metadata for logging ({ automationName, runId }) * @returns {Object} The created document (or composed data if dryRun) with warnings array */ async create(data, options) { diff --git a/app/tests/api/system-configuration/system-configuration.spec.js b/app/tests/api/system-configuration/system-configuration.spec.js index 94f36779..096cfb86 100644 --- a/app/tests/api/system-configuration/system-configuration.spec.js +++ b/app/tests/api/system-configuration/system-configuration.spec.js @@ -1,5 +1,8 @@ const request = require('supertest'); const { expect } = require('expect'); +const { + xMitrePlatformSchema, +} = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-platforms.cjs'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); @@ -96,7 +99,22 @@ describe('System Configuration API', function () { (item) => item.domainName === expectedDomainName, ); expect(domainAllowedValues).toBeDefined(); - expect(domainAllowedValues.allowedValues).toContain(expectedPropertyValue); + expect(domainAllowedValues.allowedValues).toEqual( + expect.arrayContaining([expectedPropertyValue, 'Google Workspace', 'Azure AD']), + ); + + const configuredPlatforms = [ + ...new Set( + allowedValues.flatMap((item) => + item.properties + .filter((property) => property.propertyName === expectedPropertyName) + .flatMap((property) => property.domains.flatMap((domain) => domain.allowedValues)), + ), + ), + ].sort(); + const supportedPlatforms = [...xMitrePlatformSchema.options].sort(); + + expect(configuredPlatforms).toEqual(supportedPlatforms); }); it('GET /api/config/organization-identity returns the organizaton identity', async function () { diff --git a/docs/README.md b/docs/README.md index 5d9bb62c..c952dcba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,6 +34,7 @@ Architecture, patterns, and implementation details for contributors. - [Service Exception Middleware](developer/service-exception-middleware.md): Global error handler middleware - [STIX Versioning and Embedded Relationships](developer/stix-versioning-and-embedded-relationships.md): How STIX versioning interacts with embedded relationships - [Task Scheduler](developer/task-scheduler.md): Task scheduler implementation +- [Automation Run Audit Trail](developer/automation-runs.md): Taxonomy and implementation guidance for durable automation auditing ### Release Tracks (Internals) @@ -47,6 +48,7 @@ Architecture, patterns, and implementation details for contributors. Configuration, deployment, and identity provider setup. - [Configuration](admin/configuration.md): Complete configuration guide (environment variables, JSON files) +- [Automation Run Audit Trail](admin/automation-runs.md): How to inspect migration and scheduler audit records ### Authentication diff --git a/docs/admin/automation-runs.md b/docs/admin/automation-runs.md new file mode 100644 index 00000000..1ed1905a --- /dev/null +++ b/docs/admin/automation-runs.md @@ -0,0 +1,163 @@ +# Automation Run Audit Trail + +## Overview + +Workbench persists a durable audit trail for non-human-driven +automation. This currently includes the new migration workflow and is +intended to support scheduler backfills and future repair tasks. + +Two MongoDB collections are used: + +- `automationRuns`: one summary row per automation execution +- `automationRunItems`: per-item detail linked to a parent run by + `run_id` + +These collections are operational diagnostics and audit records. They +do not replace the version history stored on ATT&CK objects +themselves. + +## When these collections are created + +The collections are created lazily the first time an automation uses +the recorder. The recorder also creates indexes needed for common +queries. + +As of now, the first built-in consumer is the +`20260507130000-normalize-x-mitre-platforms` migration. + +## What to expect in `automationRuns` + +Each run document includes: + +- `run_id`: unique identifier for the execution +- `automation_type`: coarse class such as `migration` +- `name`: specific job name +- `status`: `running`, `completed`, `partial`, or `failed` +- `started_at` and `finished_at` +- `scope`: what the job intended to operate on +- `counts`: numeric counters from the job +- `warnings`: warning counters from the job +- `verification`: post-run checks +- `summary`: human-readable outcome +- `error_summary`: terminal failure detail, if any + +## What to expect in `automationRunItems` + +Each item document includes: + +- `run_id`: parent run identifier +- `sequence`: item ordering within the run +- `status`: per-item result such as `changed`, `unchanged`, or `failed` +- `action`: operation-specific verb +- `target`: identity envelope for the processed unit +- `warnings`: optional warning codes +- `details`: automation-specific payload such as before/after values +- `error`: serialized failure detail when the item failed + +Not every automation will use exactly the same `details` payload. That +field is intentionally extensible. + +## Common operator workflows + +### Inspect the latest automation runs + +```javascript +db.automationRuns.find().sort({ started_at: -1 }).limit(10).pretty() +``` + +### Inspect the latest platform-normalization migration run + +```javascript +db.automationRuns.find( + { name: '20260507130000-normalize-x-mitre-platforms' } +).sort({ started_at: -1 }).limit(1).pretty() +``` + +### Fetch all item records for a run + +```javascript +const run = db.automationRuns.findOne( + { name: '20260507130000-normalize-x-mitre-platforms' }, + { sort: { started_at: -1 } } +); + +db.automationRunItems.find({ run_id: run.run_id }).sort({ sequence: 1 }).pretty(); +``` + +### Inspect failures only + +```javascript +db.automationRunItems.find({ + run_id: run.run_id, + status: 'failed' +}).sort({ sequence: 1 }).pretty(); +``` + +### Inspect all runs that touched a specific STIX object + +```javascript +db.automationRunItems.find({ + 'target.stix_id': 'attack-pattern--01234567-89ab-cdef-0123-456789abcdef' +}).sort({ recorded_at: -1 }).pretty(); +``` + +## Interpreting run status + +- `running`: the automation started but has not recorded terminal + state yet +- `completed`: the automation finished without item failures +- `partial`: the automation made some progress but also encountered + one or more failures +- `failed`: the automation did not complete successfully and did not + produce a usable result set + +The exact threshold between `partial` and `failed` is determined by +the automation implementation. Always inspect `counts`, +`verification`, and `error_summary` before deciding whether manual +intervention is required. + +## Interpreting verification + +`verification` contains post-run checks claimed by the automation. +For example, the platform-normalization migration records +`remaining_latest_active_objects_with_legacy_platforms`. + +This is the fastest way to answer, “Did the automation actually finish +the intended repair?” + +## Logs vs persisted records + +Container logs remain useful for real-time observation, but they are +not the system of record. The durable record is MongoDB: + +- logs are best for live troubleshooting +- `automationRuns` and `automationRunItems` are best for audit and + post-hoc analysis + +When an automation emits a `run_id` in the logs, use that `run_id` to +retrieve the persisted record in MongoDB. + +## Retention and growth + +There is currently no TTL or automatic pruning for these collections. +That is intentional: operators may want to retain a complete history +of database repairs and other automation. + +If you later decide to prune: + +1. confirm any local retention or audit requirements +2. prune old `automationRunItems` only alongside their parent + `automationRuns` +3. never confuse these records with the authoritative STIX object + history stored in `attackObjects` and `relationships` + +## Relationship to migration startup + +If automatic migrations are enabled, migrations run on server startup. +See [Configuration](configuration.md) for the +`database.migration.enable` setting. + +The automation audit trail is especially useful in environments where +migrations run automatically, because it provides durable visibility +into what happened after startup rather than relying only on container +logs. diff --git a/docs/admin/configuration.md b/docs/admin/configuration.md index b37a9caf..ba47748b 100644 --- a/docs/admin/configuration.md +++ b/docs/admin/configuration.md @@ -199,6 +199,7 @@ DATABASE_URL=mongodb://attack-workbench-database/attack-workspace - When `database.migration.enable` is `true`, migrations run automatically at startup - Set to `false` if you manage migrations separately (e.g., in a Kubernetes init container) - Migrations are idempotent and safe to run multiple times +- Automation-enabled migrations may also write durable audit records to `automationRuns` and `automationRunItems`; see [Automation Run Audit Trail](automation-runs.md) ### Application diff --git a/docs/developer/automation-runs.md b/docs/developer/automation-runs.md new file mode 100644 index 00000000..1fe464d8 --- /dev/null +++ b/docs/developer/automation-runs.md @@ -0,0 +1,315 @@ +# Automation Run Audit Trail + +## Overview + +Workbench now persists a durable audit trail for non-human-driven +write workflows such as database migrations, scheduler backfills, and +future repair tasks. + +The audit trail uses two MongoDB collections: + +- `automationRuns`: one summary document per automation execution +- `automationRunItems`: zero or more per-item outcome documents linked + to a parent run by `run_id` + +This document defines the taxonomy, the design rationale, and the +rules for extending it. + +## Design goals + +The automation-run taxonomy is meant to satisfy four requirements at +once: + +1. Provide operators with a durable record of what automation did. +2. Stay generic enough for many automation classes, not just + migrations. +3. Preserve stable, queryable top-level fields for dashboards and + tooling. +4. Avoid forcing every automation into one rigid per-item schema. + +## Dialectic design rationale + +### Thesis: a rigid, fully-normalized schema + +A strict schema is attractive because it gives easy querying and +strong conventions. For example, it is tempting to make every item +carry first-class fields like `stix_id`, `stix_type`, +`previous_modified`, `new_modified`, `changes`, and so on. + +That works well for STIX-object migrations, but it breaks down for +other automation: + +- scheduler jobs may operate on many collections +- admin repair tasks may act on one system document +- future jobs may not be versioned STIX objects at all + +If the schema is too rigid, every new automation either violates the +taxonomy or forces another schema change. + +### Antithesis: a completely free-form blob + +The opposite extreme is to store only opaque JSON blobs like +`payload` or `details` at both the run and item levels. + +That avoids schema churn, but it throws away the parts that operators +and tooling actually need to query consistently: + +- run status +- start/end timestamps +- automation class +- counts and warnings +- per-item status +- stable linkage between a run and its items + +If everything is free-form, the audit trail becomes durable but not +operationally useful. + +### Synthesis: stable envelope, extensible payload + +The chosen design is a synthesis of those two extremes: + +- The **envelope** is stable and query-oriented. +- The **payload** is extensible and automation-specific. + +That means: + +- `automationRuns` has stable top-level fields like `run_id`, + `automation_type`, `status`, `started_at`, `finished_at`, `scope`, + `counts`, `warnings`, and `verification`. +- `automationRunItems` has stable top-level fields like `run_id`, + `sequence`, `status`, `action`, `target`, `warnings`, and `error`. +- Automation-specific detail lives under `metadata` on the run and + under `details` on the item. + +This keeps the taxonomy durable while still supporting new classes of +automation without schema redesign. + +## Collection taxonomy + +### `automationRuns` + +Each document represents one execution of an automation workflow. + +Stable fields: + +| Field | Purpose | +|---|---| +| `schema_version` | Version of the automation-run document taxonomy. Increment only when the persisted shape changes incompatibly. | +| `run_id` | Unique identifier shared by the run and all its items. | +| `automation_type` | Coarse automation class, such as `migration`, `scheduler`, `backfill`, or `repair`. | +| `name` | Specific automation name, such as a migration filename or scheduler task name. | +| `status` | Current terminal or in-flight run status. Common values are `running`, `completed`, `partial`, and `failed`. | +| `started_at` | UTC start timestamp. | +| `finished_at` | UTC completion timestamp, or `null` while in flight. | +| `trigger` | How the run was initiated, for example `{ source: "startup", runner: "migrate-mongo" }`. | +| `scope` | Queryable description of what the run was meant to operate on. | +| `runtime` | Host/runtime information captured at execution time. | +| `counts` | Numeric counters recorded by the automation. | +| `warnings` | Run-level warning counters or categories. | +| `verification` | Post-run verification checks. | +| `summary` | Human-readable outcome summary. | +| `error_summary` | Terminal failure detail when the run is `partial` or `failed`. | +| `items.collection` | Collection name storing per-item detail. | + +Extensible fields: + +| Field | Purpose | +|---|---| +| `metadata` | Automation-specific configuration or context that does not belong in the stable envelope. | + +### `automationRunItems` + +Each document represents the outcome for one processed unit of work. +That unit is intentionally generic: it can be a STIX object, a Mongo +document, a collection-level operation, or a system-level step. + +Stable fields: + +| Field | Purpose | +|---|---| +| `schema_version` | Version of the item taxonomy. | +| `run_id` | Foreign key to the parent run. | +| `automation_type` | Copied from the parent run for easier filtering. | +| `name` | Copied from the parent run for easier filtering. | +| `recorded_at` | UTC timestamp for when the item outcome was recorded. | +| `sequence` | Monotonic sequence number within the run. | +| `status` | Per-item outcome status. Common values are `changed`, `unchanged`, `skipped`, and `failed`. | +| `action` | Operation-specific verb, such as `normalize_x_mitre_platforms`. | +| `target` | Stable identity envelope for what the item refers to. | +| `warnings` | Optional array of warning codes affecting this item. | +| `error` | Serialized error detail when the item failed. | + +Extensible fields: + +| Field | Purpose | +|---|---| +| `details` | Automation-specific payload, such as before/after values, attempted versions, or extra counters. | + +## Target envelope + +`target` is intentionally generic. It should contain stable identity +information for the processed unit, but not automation-specific +payload. + +Common shape: + +```json +{ + "kind": "stix-object", + "collection": "attackObjects", + "stix_id": "attack-pattern--...", + "stix_type": "attack-pattern" +} +``` + +Other valid future shapes may use: + +- `document_id` for generic Mongo documents +- `collection` only for collection-level work +- `kind: "system"` for process-wide or singleton operations + +Consumers should treat `target` as the identity envelope and `details` +as the execution payload. + +## Why `scope` and `metadata` are separate + +`scope` exists for stable, queryable descriptors of what a run was +intended to touch. For example: + +```json +{ + "collections": ["attackObjects"], + "object_kinds": ["stix-object"], + "target_types": ["attack-pattern", "tool"] +} +``` + +`metadata` is for contextual detail that may vary widely from one +automation to the next, such as a migration’s replacement mapping or +the options passed to a scheduler task. + +Rule of thumb: + +- Put stable filtering dimensions in `scope`. +- Put automation-specific context in `metadata`. + +## Example documents + +Run document: + +```json +{ + "schema_version": 1, + "run_id": "2c43b1e4-0ef4-4f3d-9e77-0dd3a5d2f8cb", + "automation_type": "migration", + "name": "20260507130000-normalize-x-mitre-platforms", + "status": "completed", + "started_at": "2026-05-07T23:31:48.800Z", + "finished_at": "2026-05-07T23:31:49.300Z", + "trigger": { + "source": "startup", + "runner": "migrate-mongo" + }, + "scope": { + "collections": ["attackObjects"], + "object_kinds": ["stix-object"], + "target_types": ["x-mitre-asset", "attack-pattern"] + }, + "counts": { + "scanned_candidates": 4, + "attempted_reposts": 4, + "updated": 4, + "unchanged": 0, + "failed": 0, + "removed_platform_field": 0 + }, + "warnings": { + "existing_validation_issues": 4 + }, + "verification": { + "remaining_latest_active_objects_with_legacy_platforms": 0 + }, + "summary": { + "message": "Normalized x_mitre_platforms on 4 active latest object(s) after scanning 4 candidate(s); removed the field entirely on 0 object(s)." + }, + "items": { + "collection": "automationRunItems" + } +} +``` + +Item document: + +```json +{ + "schema_version": 1, + "run_id": "2c43b1e4-0ef4-4f3d-9e77-0dd3a5d2f8cb", + "automation_type": "migration", + "name": "20260507130000-normalize-x-mitre-platforms", + "recorded_at": "2026-05-07T23:31:49.100Z", + "sequence": 1, + "status": "changed", + "action": "normalize_x_mitre_platforms", + "target": { + "kind": "stix-object", + "collection": "attackObjects", + "stix_id": "x-mitre-asset--68388d4f-8138-420b-be2b-5a7dfe9ff6b4", + "stix_type": "x-mitre-asset" + }, + "warnings": ["existing_validation_issues"], + "details": { + "previous_modified": "2026-05-01T12:00:00.000Z", + "new_modified": "2026-05-07T23:31:49.001Z", + "existing_validation_error_count": 3, + "changes": [ + { + "field": "stix.x_mitre_platforms", + "before": ["Network"], + "after": ["Network Devices"] + } + ] + } +} +``` + +## Authoring rules for new automation + +When adding a new migration or scheduler task: + +1. Create one run document per execution. +2. Use `automation_type` for the coarse class and `name` for the + specific job. +3. Put stable filtering dimensions in `scope`. +4. Put job-specific configuration in `metadata`. +5. Use `target` for stable identity only. +6. Put operation-specific payload in `details`. +7. Prefer `counts` and `warnings` as numeric maps instead of encoding + those metrics only in free-form text. +8. Record item rows for changed and failed units of work at minimum. + Recording unchanged items is optional and should be justified by + the operational value versus collection growth. +9. Add a post-run `verification` check whenever the automation makes a + correctness claim that can be measured. + +## Evolution policy + +The taxonomy is expected to evolve, but the stable envelope should +change slowly. + +Rules: + +1. Additive fields inside `metadata`, `details`, `counts`, + `warnings`, and `verification` are safe. +2. Additive fields in the stable envelope are acceptable when broadly + useful. +3. Renaming or deleting stable envelope fields requires bumping + `schema_version`. +4. Consumers must not assume that `details` has the same shape across + different automation names. + +## Current implementation + +The reusable recorder lives in +[`app/lib/automation-run-recorder.js`](../../app/lib/automation-run-recorder.js). +The first consumer is the platform-normalization migration in +[`migrations/20260507130000-normalize-x-mitre-platforms.js`](../../migrations/20260507130000-normalize-x-mitre-platforms.js). diff --git a/migrations/20260507130000-normalize-x-mitre-platforms.js b/migrations/20260507130000-normalize-x-mitre-platforms.js new file mode 100644 index 00000000..f8717758 --- /dev/null +++ b/migrations/20260507130000-normalize-x-mitre-platforms.js @@ -0,0 +1,463 @@ +'use strict'; + +/** + * Normalize legacy x_mitre_platforms values onto the current ATT&CK Data Model + * platform vocabulary by creating new versions of affected active latest + * objects through the normal service-layer create() workflow. + * + * Why create new versions instead of updating in place? + * - The application treats POST/create as the preferred versioned update path. + * - create() reuses the normal lifecycle hooks, validation, external reference + * rebuilding, and event emission behavior used by the API. + * + * Scope: + * - Only latest versions are considered (`stix.id` grouped by newest `stix.modified`) + * - Only active versions are considered (not revoked, not deprecated) + * - Only objects whose latest active version still contains legacy platform + * values are reposted + * + * Platform normalization rules: + * - Network -> Network Devices + * - Cloud -> IaaS, SaaS + * - Office 365 -> Office Suite + * - AWS -> IaaS + * - Azure -> IaaS + * - GCP -> IaaS + * - Device Configuration/Parameters -> removed + * + * Newly-added canonical values such as Azure AD and Google Workspace are not + * inferred automatically; the migration only rewrites existing values. + */ + +const mongoose = require('mongoose'); +const config = require('../app/config/config'); +const { + createAutomationRunRecorder, + serializeError, +} = require('../app/lib/automation-run-recorder'); +const logger = require('../app/lib/logger'); +const systemConfigurationRepository = require('../app/repository/system-configurations-repository'); +const validationBypassesService = require('../app/services/system/validation-bypasses-service'); + +const MIGRATION_NAME = '20260507130000-normalize-x-mitre-platforms'; + +const LEGACY_PLATFORM_MAPPINGS = { + Network: ['Network Devices'], + Cloud: ['IaaS', 'SaaS'], + 'Office 365': ['Office Suite'], + AWS: ['IaaS'], + Azure: ['IaaS'], + GCP: ['IaaS'], + 'Device Configuration/Parameters': [], +}; + +const LEGACY_PLATFORMS = Object.keys(LEGACY_PLATFORM_MAPPINGS); + +const TARGET_TYPES = [ + 'attack-pattern', + 'malware', + 'tool', + 'x-mitre-data-source', + 'x-mitre-asset', + 'x-mitre-analytic', +]; + +function arraysEqual(left, right) { + if (left.length !== right.length) return false; + return left.every((value, index) => value === right[index]); +} + +function normalizePlatforms(platforms) { + if (!Array.isArray(platforms)) { + return { changed: false, normalizedPlatforms: platforms }; + } + + const normalizedPlatforms = []; + + for (const platform of platforms) { + const replacements = Object.prototype.hasOwnProperty.call(LEGACY_PLATFORM_MAPPINGS, platform) + ? LEGACY_PLATFORM_MAPPINGS[platform] + : [platform]; + + for (const replacement of replacements) { + if (!normalizedPlatforms.includes(replacement)) { + normalizedPlatforms.push(replacement); + } + } + } + + return { + changed: !arraysEqual(platforms, normalizedPlatforms), + normalizedPlatforms, + }; +} + +function nextModifiedTimestamp(existingModified) { + const now = Date.now(); + const existing = new Date(existingModified).getTime(); + const next = Number.isFinite(existing) ? Math.max(now, existing + 1) : now; + return new Date(next).toISOString(); +} + +function latestActiveDocumentsWithLegacyPlatformsPipeline() { + return [ + { $match: { 'stix.type': { $in: TARGET_TYPES } } }, + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { $match: { 'stix.x_mitre_deprecated': { $in: [null, false] } } }, + { $match: { 'stix.revoked': { $in: [null, false] } } }, + { $match: { 'stix.x_mitre_platforms': { $in: LEGACY_PLATFORMS } } }, + { $project: { _id: 0, __v: 0, __t: 0 } }, + ]; +} + +async function countRemainingLatestActiveDocumentsWithLegacyPlatforms(db) { + const [result] = await db + .collection('attackObjects') + .aggregate([...latestActiveDocumentsWithLegacyPlatformsPipeline(), { $count: 'remaining' }]) + .toArray(); + + return result?.remaining || 0; +} + +function ensureMongooseUsesClient(client) { + if (mongoose.connection.readyState === 0) { + mongoose.connection.setClient(client); + } +} + +function getServiceMap() { + const techniquesService = require('../app/services/stix/techniques-service'); + const softwareService = require('../app/services/stix/software-service'); + const dataSourcesService = require('../app/services/stix/data-sources-service'); + const assetsService = require('../app/services/stix/assets-service'); + const analyticsService = require('../app/services/stix/analytics-service'); + + return { + 'attack-pattern': techniquesService, + malware: softwareService, + tool: softwareService, + 'x-mitre-data-source': dataSourcesService, + 'x-mitre-asset': assetsService, + 'x-mitre-analytic': analyticsService, + }; +} + +async function prepareServiceLayer(client) { + ensureMongooseUsesClient(client); + + await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); + + const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); + if (!systemConfig?.organization_identity_ref) { + throw new Error( + 'System configuration is missing organization_identity_ref; cannot create new versions in platform normalization migration.', + ); + } +} + +module.exports = { + async up(db, client) { + const recorder = await createAutomationRunRecorder(db, { + automationType: 'migration', + name: MIGRATION_NAME, + trigger: { + source: 'startup', + runner: 'migrate-mongo', + }, + scope: { + collections: ['attackObjects'], + object_kinds: ['stix-object'], + target_types: TARGET_TYPES, + }, + metadata: { + legacy_platform_mappings: LEGACY_PLATFORM_MAPPINGS, + }, + }); + + const counts = { + scanned_candidates: 0, + attempted_reposts: 0, + updated: 0, + unchanged: 0, + failed: 0, + removed_platform_field: 0, + }; + const warnings = { + existing_validation_issues: 0, + }; + const failures = []; + let verification = {}; + + recorder.log('info', 'Starting migration', { + targetTypes: TARGET_TYPES, + legacyPlatforms: LEGACY_PLATFORMS, + }); + + try { + await prepareServiceLayer(client); + + const serviceMap = getServiceMap(); + const cursor = db + .collection('attackObjects') + .aggregate(latestActiveDocumentsWithLegacyPlatformsPipeline()); + + while (await cursor.hasNext()) { + const document = await cursor.next(); + counts.scanned_candidates++; + + const previousPlatforms = Array.isArray(document.stix?.x_mitre_platforms) + ? document.stix.x_mitre_platforms + : []; + const { changed, normalizedPlatforms } = normalizePlatforms(previousPlatforms); + + if (!changed) { + counts.unchanged++; + await recorder.recordItem({ + status: 'unchanged', + action: 'normalize_x_mitre_platforms', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + details: { + previous_modified: document.stix?.modified, + changes: [ + { + field: 'stix.x_mitre_platforms', + before: previousPlatforms, + after: normalizedPlatforms, + }, + ], + }, + }); + continue; + } + + const service = serviceMap[document.stix?.type]; + if (!service) { + const errorMessage = `Unsupported STIX type for platform migration: ${document.stix?.type}`; + counts.failed++; + failures.push({ + stixId: document.stix?.id, + error: errorMessage, + }); + + await recorder.recordItem({ + status: 'failed', + action: 'normalize_x_mitre_platforms', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + details: { + previous_modified: document.stix?.modified, + changes: [ + { + field: 'stix.x_mitre_platforms', + before: previousPlatforms, + after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, + }, + ], + }, + error: { message: errorMessage }, + }); + recorder.log('error', 'Unsupported STIX type encountered', { + stixId: document.stix?.id, + stixType: document.stix?.type, + }); + continue; + } + + const existingValidationErrorCount = document.workspace?.validation?.errors?.length || 0; + const itemWarnings = []; + if (existingValidationErrorCount > 0) { + warnings.existing_validation_issues++; + itemWarnings.push('existing_validation_issues'); + recorder.log('warn', 'Reposting object with existing validation issues', { + stixId: document.stix?.id, + validationErrorCount: existingValidationErrorCount, + }); + } + + const repost = JSON.parse(JSON.stringify(document)); + repost.stix.modified = nextModifiedTimestamp(document.stix?.modified); + counts.attempted_reposts++; + const removesPlatformField = normalizedPlatforms.length === 0; + + if (!removesPlatformField) { + repost.stix.x_mitre_platforms = normalizedPlatforms; + } else { + delete repost.stix.x_mitre_platforms; + } + + try { + const createdDocument = await service.create(repost, { + import: false, + automationContext: { + automationName: MIGRATION_NAME, + runId: recorder.runId, + }, + }); + + counts.updated++; + if (removesPlatformField) { + counts.removed_platform_field++; + } + + await recorder.recordItem({ + status: 'changed', + action: removesPlatformField ? 'remove_x_mitre_platforms' : 'normalize_x_mitre_platforms', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + warnings: itemWarnings, + details: { + previous_modified: document.stix?.modified, + new_modified: createdDocument.stix?.modified || repost.stix.modified, + ...(existingValidationErrorCount > 0 && { + existing_validation_error_count: existingValidationErrorCount, + }), + changes: [ + { + field: 'stix.x_mitre_platforms', + before: previousPlatforms, + after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, + }, + ], + }, + }); + + recorder.log('info', 'Created normalized object version', { + stixId: document.stix?.id, + stixType: document.stix?.type, + previousModified: document.stix?.modified, + newModified: createdDocument.stix?.modified || repost.stix.modified, + before: previousPlatforms, + after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, + }); + } catch (error) { + counts.failed++; + failures.push({ + stixId: document.stix?.id, + error: error.message, + }); + + await recorder.recordItem({ + status: 'failed', + action: removesPlatformField ? 'remove_x_mitre_platforms' : 'normalize_x_mitre_platforms', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + warnings: itemWarnings, + details: { + previous_modified: document.stix?.modified, + attempted_modified: repost.stix.modified, + ...(existingValidationErrorCount > 0 && { + existing_validation_error_count: existingValidationErrorCount, + }), + changes: [ + { + field: 'stix.x_mitre_platforms', + before: previousPlatforms, + after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, + }, + ], + }, + error: serializeError(error), + }); + + recorder.log('error', 'Failed to create normalized object version', { + stixId: document.stix?.id, + stixType: document.stix?.type, + previousModified: document.stix?.modified, + attemptedModified: repost.stix.modified, + error: error.message, + }); + } + } + + verification = { + remaining_latest_active_objects_with_legacy_platforms: + await countRemainingLatestActiveDocumentsWithLegacyPlatforms(db), + }; + + if (failures.length > 0) { + throw new Error( + `Platform normalization migration failed for ${failures.length} object(s); ` + + `see automationRuns/automationRunItems for details.`, + ); + } + + const summary = { + message: + `Normalized x_mitre_platforms on ${counts.updated} active latest object(s) ` + + `after scanning ${counts.scanned_candidates} candidate(s); removed the field entirely on ` + + `${counts.removed_platform_field} object(s).`, + }; + + await recorder.finish({ + status: 'completed', + counts, + warnings, + verification, + summary, + errorSummary: null, + }); + + recorder.log('info', summary.message, { + counts, + warnings, + verification, + }); + } catch (error) { + verification = { + ...verification, + remaining_latest_active_objects_with_legacy_platforms: + verification.remaining_latest_active_objects_with_legacy_platforms ?? + (await countRemainingLatestActiveDocumentsWithLegacyPlatforms(db).catch(() => null)), + }; + + const status = counts.updated > 0 ? 'partial' : 'failed'; + + await recorder.finish({ + status, + counts, + warnings, + verification, + summary: { + message: 'Platform normalization migration did not complete successfully.', + }, + errorSummary: serializeError(error), + }); + + recorder.log('error', 'Migration failed', { + counts, + warnings, + verification, + error: error.message, + }); + + throw error; + } + }, + + async down() { + // No safe automatic rollback: the up migration creates new historical + // versions via the normal application workflow. + logger.info( + `[${MIGRATION_NAME}] down migration is a no-op: created replacement versions are retained as part of object history`, + ); + }, +}; From c56a76201425a805ecf84373992db23d15541c4c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 8 May 2026 14:46:17 -0400 Subject: [PATCH 323/370] fix(platforms): remove support for Google Workspace and Azure AD Updated allowed-values.json so Google Workspace and Azure AD are no longer listed in any enterprise x_mitre_platforms allowlist, and updated the migration script to rewrite Google Workspace to Office Suite and Azure AD to Identity Provider. Also aligned the existing, related spec.js tests. --- app/config/allowed-values.json | 8 -------- app/tests/api/data-sources/data-sources.spec.js | 2 +- .../system-configuration.spec.js | 12 +++++------- .../20260507130000-normalize-x-mitre-platforms.js | 6 +++++- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/config/allowed-values.json b/app/config/allowed-values.json index c899f079..96736e93 100644 --- a/app/config/allowed-values.json +++ b/app/config/allowed-values.json @@ -13,12 +13,10 @@ "macOS", "Windows", "Network Devices", - "Google Workspace", "SaaS", "IaaS", "Containers", "Identity Provider", - "Azure AD", "Office Suite", "ESXi" ] @@ -59,12 +57,10 @@ "macOS", "Windows", "Network Devices", - "Google Workspace", "SaaS", "IaaS", "Containers", "Identity Provider", - "Azure AD", "Office Suite", "ESXi" ] @@ -188,12 +184,10 @@ "macOS", "Windows", "Network Devices", - "Google Workspace", "IaaS", "SaaS", "Containers", "Identity Provider", - "Azure AD", "Office Suite", "ESXi" ] @@ -253,8 +247,6 @@ "SaaS", "Network Devices", "Identity Provider", - "Azure AD", - "Google Workspace", "Office Suite", "ESXi" ] diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index 36be097e..27aee363 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -32,7 +32,7 @@ const initialDataSourceData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', - x_mitre_platforms: ['macOS', 'Office 365', 'Google Workspace', 'Linux', 'Network'], + x_mitre_platforms: ['macOS', 'Office Suite', 'Identity Provider', 'Linux', 'Network Devices'], x_mitre_collection_layers: ['duis', 'laboris'], x_mitre_contributors: ['Herbert Examplecontributor'], x_mitre_domains: ['enterprise-attack'], diff --git a/app/tests/api/system-configuration/system-configuration.spec.js b/app/tests/api/system-configuration/system-configuration.spec.js index 096cfb86..7ff9f09d 100644 --- a/app/tests/api/system-configuration/system-configuration.spec.js +++ b/app/tests/api/system-configuration/system-configuration.spec.js @@ -1,8 +1,5 @@ const request = require('supertest'); const { expect } = require('expect'); -const { - xMitrePlatformSchema, -} = require('@mitre-attack/attack-data-model/dist/schemas/common/property-schemas/attack-platforms.cjs'); const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); @@ -100,8 +97,10 @@ describe('System Configuration API', function () { ); expect(domainAllowedValues).toBeDefined(); expect(domainAllowedValues.allowedValues).toEqual( - expect.arrayContaining([expectedPropertyValue, 'Google Workspace', 'Azure AD']), + expect.arrayContaining([expectedPropertyValue, 'Office Suite', 'Identity Provider']), ); + expect(domainAllowedValues.allowedValues).not.toContain('Google Workspace'); + expect(domainAllowedValues.allowedValues).not.toContain('Azure AD'); const configuredPlatforms = [ ...new Set( @@ -112,9 +111,8 @@ describe('System Configuration API', function () { ), ), ].sort(); - const supportedPlatforms = [...xMitrePlatformSchema.options].sort(); - - expect(configuredPlatforms).toEqual(supportedPlatforms); + expect(configuredPlatforms).not.toContain('Google Workspace'); + expect(configuredPlatforms).not.toContain('Azure AD'); }); it('GET /api/config/organization-identity returns the organizaton identity', async function () { diff --git a/migrations/20260507130000-normalize-x-mitre-platforms.js b/migrations/20260507130000-normalize-x-mitre-platforms.js index f8717758..fe3f25f4 100644 --- a/migrations/20260507130000-normalize-x-mitre-platforms.js +++ b/migrations/20260507130000-normalize-x-mitre-platforms.js @@ -20,12 +20,14 @@ * - Network -> Network Devices * - Cloud -> IaaS, SaaS * - Office 365 -> Office Suite + * - Google Workspace -> Office Suite * - AWS -> IaaS * - Azure -> IaaS * - GCP -> IaaS + * - Azure AD -> Identity Provider * - Device Configuration/Parameters -> removed * - * Newly-added canonical values such as Azure AD and Google Workspace are not + * Newly-added canonical values such as Identity Provider and Office Suite are not * inferred automatically; the migration only rewrites existing values. */ @@ -45,9 +47,11 @@ const LEGACY_PLATFORM_MAPPINGS = { Network: ['Network Devices'], Cloud: ['IaaS', 'SaaS'], 'Office 365': ['Office Suite'], + 'Google Workspace': ['Office Suite'], AWS: ['IaaS'], Azure: ['IaaS'], GCP: ['IaaS'], + 'Azure AD': ['Identity Provider'], 'Device Configuration/Parameters': [], }; From 56cb7898734d63a513ec94a7f175567a6072da79 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 8 May 2026 14:55:22 -0400 Subject: [PATCH 324/370] feat(validation): bump ADM to v4.11.1 to reflect updated platform v4.11.1 removes support for Google Workspace and Azure AD --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a427560..cbc7a0f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.0", + "@mitre-attack/attack-data-model": "^4.11.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -878,9 +878,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.0.tgz", - "integrity": "sha512-U9CsivFwg6EVeM4edjNQYm6LJXbQbNzIcvixjRSdp5qOCFRi2UwL1CVa2QwE9ZXuNN19ZOH9eEdQcmmL+uowOA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.1.tgz", + "integrity": "sha512-YBqiUZ66jw22Z+KmYhYC9if64MJLueqDeZ91TS6wC8nUtXFycFAbgkbmXDVS2GgxSDcVa32EpFbj6+WRtiwaVQ==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index 22f29198..86c8d878 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.0", + "@mitre-attack/attack-data-model": "^4.11.1", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", From 7e051942f57952d085d3154bc3754078627dc1b2 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 8 May 2026 15:00:54 -0400 Subject: [PATCH 325/370] docs(developer): improve design rationale section in automation-runs.md --- docs/developer/automation-runs.md | 73 ++++++++++++------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/docs/developer/automation-runs.md b/docs/developer/automation-runs.md index 1fe464d8..1659cd7a 100644 --- a/docs/developer/automation-runs.md +++ b/docs/developer/automation-runs.md @@ -27,51 +27,36 @@ once: tooling. 4. Avoid forcing every automation into one rigid per-item schema. -## Dialectic design rationale +## Design rationale -### Thesis: a rigid, fully-normalized schema - -A strict schema is attractive because it gives easy querying and -strong conventions. For example, it is tempting to make every item -carry first-class fields like `stix_id`, `stix_type`, +The obvious starting point is a strict, fully-normalized schema. It +makes querying easy and forces strong conventions, and it is tempting +to give every item first-class fields like `stix_id`, `stix_type`, `previous_modified`, `new_modified`, `changes`, and so on. -That works well for STIX-object migrations, but it breaks down for -other automation: - -- scheduler jobs may operate on many collections -- admin repair tasks may act on one system document -- future jobs may not be versioned STIX objects at all - -If the schema is too rigid, every new automation either violates the -taxonomy or forces another schema change. - -### Antithesis: a completely free-form blob - -The opposite extreme is to store only opaque JSON blobs like -`payload` or `details` at both the run and item levels. - -That avoids schema churn, but it throws away the parts that operators -and tooling actually need to query consistently: - -- run status -- start/end timestamps -- automation class -- counts and warnings -- per-item status -- stable linkage between a run and its items - -If everything is free-form, the audit trail becomes durable but not -operationally useful. - -### Synthesis: stable envelope, extensible payload - -The chosen design is a synthesis of those two extremes: - -- The **envelope** is stable and query-oriented. -- The **payload** is extensible and automation-specific. - -That means: +That fits STIX-object migrations cleanly, but it starts to chafe as +soon as the next automation looks slightly different. Scheduler jobs +may operate on many collections. Admin repair tasks may act on one +system document. Future jobs may not be versioned STIX objects at +all. A taxonomy that hard-codes the shape of a STIX migration forces +every later automation to either violate the schema or trigger +another round of schema changes. + +The reflex when that becomes painful is to retreat to the other +extreme: store only opaque JSON blobs — a `payload` or `details` +field at both the run and item levels — and let each automation +describe itself however it likes. That removes schema churn entirely, +but it also removes the parts operators and tooling actually rely on: +run status, start and end timestamps, automation class, counts and +warnings, per-item status, and the linkage between a run and its +items. A purely free-form audit trail is durable but not operationally +useful. + +The split that falls out of that tension is the design used here. The +parts every automation must expose for querying and dashboards — the +**envelope** — stay stable and structured. The parts that legitimately +vary by automation class — the **payload** — stay extensible and +free-form. Concretely: - `automationRuns` has stable top-level fields like `run_id`, `automation_type`, `status`, `started_at`, `finished_at`, `scope`, @@ -81,8 +66,8 @@ That means: - Automation-specific detail lives under `metadata` on the run and under `details` on the item. -This keeps the taxonomy durable while still supporting new classes of -automation without schema redesign. +That keeps the taxonomy durable for tooling while still absorbing new +classes of automation without a schema redesign. ## Collection taxonomy From f6c228034b69beceeef2dda02250a917c810c31b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 11 May 2026 15:03:47 -0400 Subject: [PATCH 326/370] fix(validation): upgrade ADM to v4.10.5 - assets: make related_asset_sectors and description required in x_mitre_related_assets elements - bundle: allow bundle parsing under Zod v4 pick-with-refinements restriction - analytics: validate no duplicate mutable elements - techniques: at least one kill chain phase is required --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index cbc7a0f8..9b40fbd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.1", + "@mitre-attack/attack-data-model": "^4.11.5", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -878,9 +878,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.1.tgz", - "integrity": "sha512-YBqiUZ66jw22Z+KmYhYC9if64MJLueqDeZ91TS6wC8nUtXFycFAbgkbmXDVS2GgxSDcVa32EpFbj6+WRtiwaVQ==", + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.5.tgz", + "integrity": "sha512-z2plVSQjK6M8wzqBaV43ln0IxjJQJii0MgpK4LNUHqlFRicTRoFNPR53QLXlT/d9jUEWO54RN09Yo9DX+/I2gA==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index 86c8d878..f2bd37c3 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.1", + "@mitre-attack/attack-data-model": "^4.11.5", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", From db739412eed5f0b456b1a7ef80d828cb19d09a7f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 11 May 2026 15:18:36 -0400 Subject: [PATCH 327/370] feat(migrations): normalize x_mitre_mutable_elements on analytics Add migration to deduplicate x_mitre_mutable_elements entries on the latest active version of each analytic. ADM v4.11.5 introduced a Zod refinement that rejects analytics whose mutable elements contain duplicate (field, description) tuples; this migration brings existing data into compliance. Reposts go through analyticsService.create() so normal lifecycle hooks, validation, and event emission fire on the new version, and the run is recorded via the automation-run audit trail (envelope + per-item details, with a post-run verification count). --- ...0000-normalize-x-mitre-mutable-elements.js | 406 ++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 migrations/20260511130000-normalize-x-mitre-mutable-elements.js diff --git a/migrations/20260511130000-normalize-x-mitre-mutable-elements.js b/migrations/20260511130000-normalize-x-mitre-mutable-elements.js new file mode 100644 index 00000000..aeb48e86 --- /dev/null +++ b/migrations/20260511130000-normalize-x-mitre-mutable-elements.js @@ -0,0 +1,406 @@ +'use strict'; + +/** + * Normalize x_mitre_mutable_elements on analytics by removing duplicate + * entries, where uniqueness is defined by the composite (field, description) + * tuple. ADM v4.11.5 introduced a Zod refinement that rejects analytics with + * duplicate mutable elements; this migration brings existing data into + * compliance with that rule. + * + * Why create new versions instead of updating in place? + * - The application treats POST/create as the preferred versioned update path. + * - create() reuses the normal lifecycle hooks, validation, external reference + * rebuilding, and event emission behavior used by the API. + * + * Scope: + * - Only x-mitre-analytic objects are considered + * - Only latest versions are considered (`stix.id` grouped by newest `stix.modified`) + * - Only active versions are considered (not revoked, not deprecated) + * - Only objects whose latest active version contains duplicate mutable + * elements are reposted + * + * Normalization rule: + * - For each analytic, walk x_mitre_mutable_elements in order and keep only + * the first occurrence of each (field, description) tuple. Later duplicates + * are dropped. Order of first occurrences is preserved. + */ + +const mongoose = require('mongoose'); +const config = require('../app/config/config'); +const { + createAutomationRunRecorder, + serializeError, +} = require('../app/lib/automation-run-recorder'); +const logger = require('../app/lib/logger'); +const systemConfigurationRepository = require('../app/repository/system-configurations-repository'); +const validationBypassesService = require('../app/services/system/validation-bypasses-service'); + +const MIGRATION_NAME = '20260511130000-normalize-x-mitre-mutable-elements'; + +const TARGET_TYPE = 'x-mitre-analytic'; + +function dedupeKey(element) { + return JSON.stringify([element?.field ?? null, element?.description ?? null]); +} + +function deduplicateMutableElements(mutableElements) { + if (!Array.isArray(mutableElements)) { + return { changed: false, deduplicated: mutableElements, removedCount: 0 }; + } + + const seen = new Set(); + const deduplicated = []; + + for (const element of mutableElements) { + const key = dedupeKey(element); + if (seen.has(key)) continue; + seen.add(key); + deduplicated.push(element); + } + + return { + changed: deduplicated.length !== mutableElements.length, + deduplicated, + removedCount: mutableElements.length - deduplicated.length, + }; +} + +function nextModifiedTimestamp(existingModified) { + const now = Date.now(); + const existing = new Date(existingModified).getTime(); + const next = Number.isFinite(existing) ? Math.max(now, existing + 1) : now; + return new Date(next).toISOString(); +} + +function latestActiveAnalyticsWithDuplicateMutableElementsPipeline() { + return [ + { $match: { 'stix.type': TARGET_TYPE } }, + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { $match: { 'stix.x_mitre_deprecated': { $in: [null, false] } } }, + { $match: { 'stix.revoked': { $in: [null, false] } } }, + { + $match: { + 'stix.x_mitre_mutable_elements': { $type: 'array', $not: { $size: 0 } }, + }, + }, + { + $addFields: { + __mutable_element_count: { $size: '$stix.x_mitre_mutable_elements' }, + __unique_mutable_element_count: { + $size: { + $setUnion: [ + { + $map: { + input: '$stix.x_mitre_mutable_elements', + as: 'el', + in: { field: '$$el.field', description: '$$el.description' }, + }, + }, + [], + ], + }, + }, + }, + }, + { + $match: { + $expr: { $ne: ['$__mutable_element_count', '$__unique_mutable_element_count'] }, + }, + }, + { $project: { _id: 0, __v: 0, __t: 0, __mutable_element_count: 0, __unique_mutable_element_count: 0 } }, + ]; +} + +async function countRemainingLatestActiveAnalyticsWithDuplicateMutableElements(db) { + const [result] = await db + .collection('attackObjects') + .aggregate([ + ...latestActiveAnalyticsWithDuplicateMutableElementsPipeline(), + { $count: 'remaining' }, + ]) + .toArray(); + + return result?.remaining || 0; +} + +function ensureMongooseUsesClient(client) { + if (mongoose.connection.readyState === 0) { + mongoose.connection.setClient(client); + } +} + +async function prepareServiceLayer(client) { + ensureMongooseUsesClient(client); + + await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); + + const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); + if (!systemConfig?.organization_identity_ref) { + throw new Error( + 'System configuration is missing organization_identity_ref; cannot create new versions in mutable-elements normalization migration.', + ); + } +} + +module.exports = { + async up(db, client) { + const recorder = await createAutomationRunRecorder(db, { + automationType: 'migration', + name: MIGRATION_NAME, + trigger: { + source: 'startup', + runner: 'migrate-mongo', + }, + scope: { + collections: ['attackObjects'], + object_kinds: ['stix-object'], + target_types: [TARGET_TYPE], + }, + metadata: { + dedup_composite_key: ['field', 'description'], + }, + }); + + const counts = { + scanned_candidates: 0, + attempted_reposts: 0, + updated: 0, + unchanged: 0, + failed: 0, + duplicates_removed: 0, + }; + const warnings = { + existing_validation_issues: 0, + }; + const failures = []; + let verification = {}; + + recorder.log('info', 'Starting migration', { + targetType: TARGET_TYPE, + }); + + try { + await prepareServiceLayer(client); + + const analyticsService = require('../app/services/stix/analytics-service'); + + const cursor = db + .collection('attackObjects') + .aggregate(latestActiveAnalyticsWithDuplicateMutableElementsPipeline()); + + while (await cursor.hasNext()) { + const document = await cursor.next(); + counts.scanned_candidates++; + + const previousMutableElements = Array.isArray(document.stix?.x_mitre_mutable_elements) + ? document.stix.x_mitre_mutable_elements + : []; + const { changed, deduplicated, removedCount } = + deduplicateMutableElements(previousMutableElements); + + if (!changed) { + counts.unchanged++; + await recorder.recordItem({ + status: 'unchanged', + action: 'normalize_x_mitre_mutable_elements', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + details: { + previous_modified: document.stix?.modified, + changes: [ + { + field: 'stix.x_mitre_mutable_elements', + before: previousMutableElements, + after: deduplicated, + }, + ], + }, + }); + continue; + } + + const existingValidationErrorCount = document.workspace?.validation?.errors?.length || 0; + const itemWarnings = []; + if (existingValidationErrorCount > 0) { + warnings.existing_validation_issues++; + itemWarnings.push('existing_validation_issues'); + recorder.log('warn', 'Reposting analytic with existing validation issues', { + stixId: document.stix?.id, + validationErrorCount: existingValidationErrorCount, + }); + } + + const repost = JSON.parse(JSON.stringify(document)); + repost.stix.modified = nextModifiedTimestamp(document.stix?.modified); + repost.stix.x_mitre_mutable_elements = deduplicated; + counts.attempted_reposts++; + + try { + const createdDocument = await analyticsService.create(repost, { + import: false, + automationContext: { + automationName: MIGRATION_NAME, + runId: recorder.runId, + }, + }); + + counts.updated++; + counts.duplicates_removed += removedCount; + + await recorder.recordItem({ + status: 'changed', + action: 'normalize_x_mitre_mutable_elements', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + warnings: itemWarnings, + details: { + previous_modified: document.stix?.modified, + new_modified: createdDocument.stix?.modified || repost.stix.modified, + duplicates_removed: removedCount, + ...(existingValidationErrorCount > 0 && { + existing_validation_error_count: existingValidationErrorCount, + }), + changes: [ + { + field: 'stix.x_mitre_mutable_elements', + before: previousMutableElements, + after: deduplicated, + }, + ], + }, + }); + + recorder.log('info', 'Created normalized analytic version', { + stixId: document.stix?.id, + previousModified: document.stix?.modified, + newModified: createdDocument.stix?.modified || repost.stix.modified, + duplicatesRemoved: removedCount, + }); + } catch (error) { + counts.failed++; + failures.push({ + stixId: document.stix?.id, + error: error.message, + }); + + await recorder.recordItem({ + status: 'failed', + action: 'normalize_x_mitre_mutable_elements', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + warnings: itemWarnings, + details: { + previous_modified: document.stix?.modified, + attempted_modified: repost.stix.modified, + duplicates_removed: removedCount, + ...(existingValidationErrorCount > 0 && { + existing_validation_error_count: existingValidationErrorCount, + }), + changes: [ + { + field: 'stix.x_mitre_mutable_elements', + before: previousMutableElements, + after: deduplicated, + }, + ], + }, + error: serializeError(error), + }); + + recorder.log('error', 'Failed to create normalized analytic version', { + stixId: document.stix?.id, + previousModified: document.stix?.modified, + attemptedModified: repost.stix.modified, + error: error.message, + }); + } + } + + verification = { + remaining_latest_active_analytics_with_duplicate_mutable_elements: + await countRemainingLatestActiveAnalyticsWithDuplicateMutableElements(db), + }; + + if (failures.length > 0) { + throw new Error( + `Mutable-elements normalization migration failed for ${failures.length} analytic(s); ` + + `see automationRuns/automationRunItems for details.`, + ); + } + + const summary = { + message: + `Deduplicated x_mitre_mutable_elements on ${counts.updated} active latest analytic(s) ` + + `after scanning ${counts.scanned_candidates} candidate(s); removed ${counts.duplicates_removed} duplicate entry/entries in total.`, + }; + + await recorder.finish({ + status: 'completed', + counts, + warnings, + verification, + summary, + errorSummary: null, + }); + + recorder.log('info', summary.message, { + counts, + warnings, + verification, + }); + } catch (error) { + verification = { + ...verification, + remaining_latest_active_analytics_with_duplicate_mutable_elements: + verification.remaining_latest_active_analytics_with_duplicate_mutable_elements ?? + (await countRemainingLatestActiveAnalyticsWithDuplicateMutableElements(db).catch( + () => null, + )), + }; + + const status = counts.updated > 0 ? 'partial' : 'failed'; + + await recorder.finish({ + status, + counts, + warnings, + verification, + summary: { + message: 'Mutable-elements normalization migration did not complete successfully.', + }, + errorSummary: serializeError(error), + }); + + recorder.log('error', 'Migration failed', { + counts, + warnings, + verification, + error: error.message, + }); + + throw error; + } + }, + + async down() { + // No safe automatic rollback: the up migration creates new historical + // versions via the normal application workflow. + logger.info( + `[${MIGRATION_NAME}] down migration is a no-op: created replacement versions are retained as part of object history`, + ); + }, +}; From f02c31aa5cbae373c7d7e6d72735f79072d459e7 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 11 May 2026 15:32:41 -0400 Subject: [PATCH 328/370] style: apply formatting --- ...0260511130000-normalize-x-mitre-mutable-elements.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/migrations/20260511130000-normalize-x-mitre-mutable-elements.js b/migrations/20260511130000-normalize-x-mitre-mutable-elements.js index aeb48e86..f5acabb9 100644 --- a/migrations/20260511130000-normalize-x-mitre-mutable-elements.js +++ b/migrations/20260511130000-normalize-x-mitre-mutable-elements.js @@ -109,7 +109,15 @@ function latestActiveAnalyticsWithDuplicateMutableElementsPipeline() { $expr: { $ne: ['$__mutable_element_count', '$__unique_mutable_element_count'] }, }, }, - { $project: { _id: 0, __v: 0, __t: 0, __mutable_element_count: 0, __unique_mutable_element_count: 0 } }, + { + $project: { + _id: 0, + __v: 0, + __t: 0, + __mutable_element_count: 0, + __unique_mutable_element_count: 0, + }, + }, ]; } From 9b0fb8033cadd8ce625baeec9bc2dd27e22861c8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 11 May 2026 15:33:42 -0400 Subject: [PATCH 329/370] feat(migrations): demote assets with noncompliant related_assets Add migration that detects active, latest x-mitre-asset objects in 'reviewed' or 'awaiting-review' state whose x_mitre_related_assets entries violate ADM v4.11.2's new required-field rules on and , and reposts each as a new revision with workflow state demoted to 'work-in-progress'. Demotion is the documented escape valve for this kind of schema tightening: 'work-in-progress' maps to ADM's partial schema, so the new revision validates successfully while authors fill in the missing fields. The repost goes through assetsService.create() so the new (stix.id, stix.modified) pair is produced via the normal lifecycle, and the run is recorded via the automation-run audit trail (envelope + per-item offender list, with a post-run verification count). --- ...emote-noncompliant-asset-related-assets.js | 408 ++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 migrations/20260511140000-demote-noncompliant-asset-related-assets.js diff --git a/migrations/20260511140000-demote-noncompliant-asset-related-assets.js b/migrations/20260511140000-demote-noncompliant-asset-related-assets.js new file mode 100644 index 00000000..20cc612f --- /dev/null +++ b/migrations/20260511140000-demote-noncompliant-asset-related-assets.js @@ -0,0 +1,408 @@ +'use strict'; + +/** + * Repost active, latest x-mitre-asset objects whose x_mitre_related_assets + * entries violate ADM v4.11.2's new required-field rules on `description` and + * `related_asset_sectors`. The new revision is identical to the prior version + * except that its workflow state is demoted to 'work-in-progress'. + * + * Why create a new revision instead of updating in place? + * - In Workbench, object revisions are first-class. POST/create is the + * canonical versioned update path: each revision is a new MongoDB document, + * uniquely identified by (stix.id, stix.modified). Updating workspace fields + * in place would silently mutate historical state. + * - create() reuses the normal lifecycle hooks, validation, external reference + * rebuilding, and event emission behavior used by the API. + * + * Why demote to 'work-in-progress'? + * - Validation in the service layer is workflow-state-aware: 'work-in-progress' + * maps to ADM's partial schema, which permits the now-required fields to be + * omitted while authors fill them in. That makes the demoted revision the + * documented escape valve for this kind of schema tightening, and it is the + * reason create() succeeds on objects that would otherwise fail validation. + * + * Scope: + * - Only x-mitre-asset objects are considered + * - Only latest versions are considered (`stix.id` grouped by newest `stix.modified`) + * - Only active versions are considered (not revoked, not deprecated) + * - Only versions currently in 'reviewed' or 'awaiting-review' are considered + * - Only versions whose x_mitre_related_assets contains at least one entry + * missing/unset `description` or `related_asset_sectors` + */ + +const mongoose = require('mongoose'); +const config = require('../app/config/config'); +const { + createAutomationRunRecorder, + serializeError, +} = require('../app/lib/automation-run-recorder'); +const logger = require('../app/lib/logger'); +const systemConfigurationRepository = require('../app/repository/system-configurations-repository'); +const validationBypassesService = require('../app/services/system/validation-bypasses-service'); + +const MIGRATION_NAME = '20260511140000-demote-noncompliant-asset-related-assets'; + +const TARGET_TYPE = 'x-mitre-asset'; +const REVIEW_STATES = ['reviewed', 'awaiting-review']; +const DEMOTED_STATE = 'work-in-progress'; + +function isMissingDescription(relatedAsset) { + const value = relatedAsset?.description; + return ( + value === undefined || value === null || (typeof value === 'string' && value.trim() === '') + ); +} + +function isMissingRelatedAssetSectors(relatedAsset) { + const value = relatedAsset?.related_asset_sectors; + return value === undefined || value === null || (Array.isArray(value) && value.length === 0); +} + +function findOffendingRelatedAssets(relatedAssets) { + if (!Array.isArray(relatedAssets)) return []; + + const offenders = []; + relatedAssets.forEach((relatedAsset, index) => { + const missingDescription = isMissingDescription(relatedAsset); + const missingSectors = isMissingRelatedAssetSectors(relatedAsset); + if (missingDescription || missingSectors) { + offenders.push({ + index, + name: relatedAsset?.name, + missing_fields: [ + ...(missingDescription ? ['description'] : []), + ...(missingSectors ? ['related_asset_sectors'] : []), + ], + }); + } + }); + + return offenders; +} + +function nextModifiedTimestamp(existingModified) { + const now = Date.now(); + const existing = new Date(existingModified).getTime(); + const next = Number.isFinite(existing) ? Math.max(now, existing + 1) : now; + return new Date(next).toISOString(); +} + +function latestActiveAssetsInReviewStatesPipeline() { + return [ + { $match: { 'stix.type': TARGET_TYPE } }, + { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, + { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, + { $replaceRoot: { newRoot: '$document' } }, + { $match: { 'stix.x_mitre_deprecated': { $in: [null, false] } } }, + { $match: { 'stix.revoked': { $in: [null, false] } } }, + { $match: { 'workspace.workflow.state': { $in: REVIEW_STATES } } }, + { + $match: { + 'stix.x_mitre_related_assets': { $type: 'array', $not: { $size: 0 } }, + }, + }, + { + $match: { + $expr: { + $anyElementTrue: { + $map: { + input: { $ifNull: ['$stix.x_mitre_related_assets', []] }, + as: 'ra', + in: { + $or: [ + { $eq: [{ $ifNull: ['$$ra.description', null] }, null] }, + { $eq: [{ $ifNull: ['$$ra.description', ''] }, ''] }, + { + $eq: [{ $size: { $ifNull: ['$$ra.related_asset_sectors', []] } }, 0], + }, + ], + }, + }, + }, + }, + }, + }, + { $project: { _id: 0, __v: 0, __t: 0 } }, + ]; +} + +async function countRemainingNoncompliantReviewedAssets(db) { + const [result] = await db + .collection('attackObjects') + .aggregate([...latestActiveAssetsInReviewStatesPipeline(), { $count: 'remaining' }]) + .toArray(); + + return result?.remaining || 0; +} + +function ensureMongooseUsesClient(client) { + if (mongoose.connection.readyState === 0) { + mongoose.connection.setClient(client); + } +} + +async function prepareServiceLayer(client) { + ensureMongooseUsesClient(client); + + await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); + + const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); + if (!systemConfig?.organization_identity_ref) { + throw new Error( + 'System configuration is missing organization_identity_ref; cannot create new versions in asset related-assets demotion migration.', + ); + } +} + +module.exports = { + async up(db, client) { + const recorder = await createAutomationRunRecorder(db, { + automationType: 'migration', + name: MIGRATION_NAME, + trigger: { + source: 'startup', + runner: 'migrate-mongo', + }, + scope: { + collections: ['attackObjects'], + object_kinds: ['stix-object'], + target_types: [TARGET_TYPE], + review_states: REVIEW_STATES, + }, + metadata: { + required_related_asset_fields: ['description', 'related_asset_sectors'], + demoted_state: DEMOTED_STATE, + }, + }); + + const counts = { + scanned_candidates: 0, + attempted_reposts: 0, + updated: 0, + unchanged: 0, + failed: 0, + }; + const warnings = { + existing_validation_issues: 0, + }; + const failures = []; + let verification = {}; + + recorder.log('info', 'Starting migration', { + targetType: TARGET_TYPE, + reviewStates: REVIEW_STATES, + demotedState: DEMOTED_STATE, + }); + + try { + await prepareServiceLayer(client); + + const assetsService = require('../app/services/stix/assets-service'); + + const cursor = db + .collection('attackObjects') + .aggregate(latestActiveAssetsInReviewStatesPipeline()); + + while (await cursor.hasNext()) { + const document = await cursor.next(); + counts.scanned_candidates++; + + const previousState = document.workspace?.workflow?.state; + const offenders = findOffendingRelatedAssets(document.stix?.x_mitre_related_assets); + + if (offenders.length === 0) { + // Pipeline matched but no offenders identified in application code — + // record as unchanged for observability rather than silently skipping. + counts.unchanged++; + await recorder.recordItem({ + status: 'unchanged', + action: 'demote_workflow_state', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + details: { + previous_modified: document.stix?.modified, + previous_state: previousState, + reason: 'no_offending_related_assets_after_application_recheck', + }, + }); + continue; + } + + const existingValidationErrorCount = document.workspace?.validation?.errors?.length || 0; + const itemWarnings = []; + if (existingValidationErrorCount > 0) { + warnings.existing_validation_issues++; + itemWarnings.push('existing_validation_issues'); + recorder.log('warn', 'Reposting asset with existing validation issues', { + stixId: document.stix?.id, + validationErrorCount: existingValidationErrorCount, + }); + } + + const repost = JSON.parse(JSON.stringify(document)); + repost.stix.modified = nextModifiedTimestamp(document.stix?.modified); + repost.workspace = repost.workspace || {}; + repost.workspace.workflow = repost.workspace.workflow || {}; + repost.workspace.workflow.state = DEMOTED_STATE; + counts.attempted_reposts++; + + try { + const createdDocument = await assetsService.create(repost, { + import: false, + automationContext: { + automationName: MIGRATION_NAME, + runId: recorder.runId, + }, + }); + + counts.updated++; + + await recorder.recordItem({ + status: 'changed', + action: 'demote_workflow_state', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + warnings: itemWarnings, + details: { + previous_modified: document.stix?.modified, + new_modified: createdDocument.stix?.modified || repost.stix.modified, + offending_related_assets: offenders, + ...(existingValidationErrorCount > 0 && { + existing_validation_error_count: existingValidationErrorCount, + }), + changes: [ + { + field: 'workspace.workflow.state', + before: previousState, + after: DEMOTED_STATE, + }, + ], + }, + }); + + recorder.log('info', 'Created demoted asset revision', { + stixId: document.stix?.id, + previousModified: document.stix?.modified, + newModified: createdDocument.stix?.modified || repost.stix.modified, + previousState, + newState: DEMOTED_STATE, + offenderCount: offenders.length, + }); + } catch (error) { + counts.failed++; + failures.push({ + stixId: document.stix?.id, + error: error.message, + }); + + await recorder.recordItem({ + status: 'failed', + action: 'demote_workflow_state', + target: { + kind: 'stix-object', + collection: 'attackObjects', + stix_id: document.stix?.id, + stix_type: document.stix?.type, + }, + warnings: itemWarnings, + details: { + previous_modified: document.stix?.modified, + attempted_modified: repost.stix.modified, + previous_state: previousState, + offending_related_assets: offenders, + ...(existingValidationErrorCount > 0 && { + existing_validation_error_count: existingValidationErrorCount, + }), + }, + error: serializeError(error), + }); + + recorder.log('error', 'Failed to create demoted asset revision', { + stixId: document.stix?.id, + previousModified: document.stix?.modified, + attemptedModified: repost.stix.modified, + error: error.message, + }); + } + } + + verification = { + remaining_latest_active_reviewed_assets_with_noncompliant_related_assets: + await countRemainingNoncompliantReviewedAssets(db), + }; + + if (failures.length > 0) { + throw new Error( + `Asset related-assets demotion migration failed for ${failures.length} object(s); ` + + `see automationRuns/automationRunItems for details.`, + ); + } + + const summary = { + message: + `Demoted ${counts.updated} active latest asset(s) to '${DEMOTED_STATE}' ` + + `after scanning ${counts.scanned_candidates} candidate(s).`, + }; + + await recorder.finish({ + status: 'completed', + counts, + warnings, + verification, + summary, + errorSummary: null, + }); + + recorder.log('info', summary.message, { + counts, + warnings, + verification, + }); + } catch (error) { + verification = { + ...verification, + remaining_latest_active_reviewed_assets_with_noncompliant_related_assets: + verification.remaining_latest_active_reviewed_assets_with_noncompliant_related_assets ?? + (await countRemainingNoncompliantReviewedAssets(db).catch(() => null)), + }; + + const status = counts.updated > 0 ? 'partial' : 'failed'; + + await recorder.finish({ + status, + counts, + warnings, + verification, + summary: { + message: 'Asset related-assets demotion migration did not complete successfully.', + }, + errorSummary: serializeError(error), + }); + + recorder.log('error', 'Migration failed', { + counts, + warnings, + verification, + error: error.message, + }); + + throw error; + } + }, + + async down() { + // No safe automatic rollback: the up migration creates new historical + // revisions via the normal application workflow. + logger.info( + `[${MIGRATION_NAME}] down migration is a no-op: created replacement revisions are retained as part of object history`, + ); + }, +}; From d37bc2c32e9e4fabb6985fdc797680d4b26ea8c4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 11 May 2026 16:22:26 -0400 Subject: [PATCH 330/370] fix(migrations): remove 20260507130000-normalize-x-mitre-platforms.js --- ...60507130000-normalize-x-mitre-platforms.js | 467 ------------------ 1 file changed, 467 deletions(-) delete mode 100644 migrations/20260507130000-normalize-x-mitre-platforms.js diff --git a/migrations/20260507130000-normalize-x-mitre-platforms.js b/migrations/20260507130000-normalize-x-mitre-platforms.js deleted file mode 100644 index fe3f25f4..00000000 --- a/migrations/20260507130000-normalize-x-mitre-platforms.js +++ /dev/null @@ -1,467 +0,0 @@ -'use strict'; - -/** - * Normalize legacy x_mitre_platforms values onto the current ATT&CK Data Model - * platform vocabulary by creating new versions of affected active latest - * objects through the normal service-layer create() workflow. - * - * Why create new versions instead of updating in place? - * - The application treats POST/create as the preferred versioned update path. - * - create() reuses the normal lifecycle hooks, validation, external reference - * rebuilding, and event emission behavior used by the API. - * - * Scope: - * - Only latest versions are considered (`stix.id` grouped by newest `stix.modified`) - * - Only active versions are considered (not revoked, not deprecated) - * - Only objects whose latest active version still contains legacy platform - * values are reposted - * - * Platform normalization rules: - * - Network -> Network Devices - * - Cloud -> IaaS, SaaS - * - Office 365 -> Office Suite - * - Google Workspace -> Office Suite - * - AWS -> IaaS - * - Azure -> IaaS - * - GCP -> IaaS - * - Azure AD -> Identity Provider - * - Device Configuration/Parameters -> removed - * - * Newly-added canonical values such as Identity Provider and Office Suite are not - * inferred automatically; the migration only rewrites existing values. - */ - -const mongoose = require('mongoose'); -const config = require('../app/config/config'); -const { - createAutomationRunRecorder, - serializeError, -} = require('../app/lib/automation-run-recorder'); -const logger = require('../app/lib/logger'); -const systemConfigurationRepository = require('../app/repository/system-configurations-repository'); -const validationBypassesService = require('../app/services/system/validation-bypasses-service'); - -const MIGRATION_NAME = '20260507130000-normalize-x-mitre-platforms'; - -const LEGACY_PLATFORM_MAPPINGS = { - Network: ['Network Devices'], - Cloud: ['IaaS', 'SaaS'], - 'Office 365': ['Office Suite'], - 'Google Workspace': ['Office Suite'], - AWS: ['IaaS'], - Azure: ['IaaS'], - GCP: ['IaaS'], - 'Azure AD': ['Identity Provider'], - 'Device Configuration/Parameters': [], -}; - -const LEGACY_PLATFORMS = Object.keys(LEGACY_PLATFORM_MAPPINGS); - -const TARGET_TYPES = [ - 'attack-pattern', - 'malware', - 'tool', - 'x-mitre-data-source', - 'x-mitre-asset', - 'x-mitre-analytic', -]; - -function arraysEqual(left, right) { - if (left.length !== right.length) return false; - return left.every((value, index) => value === right[index]); -} - -function normalizePlatforms(platforms) { - if (!Array.isArray(platforms)) { - return { changed: false, normalizedPlatforms: platforms }; - } - - const normalizedPlatforms = []; - - for (const platform of platforms) { - const replacements = Object.prototype.hasOwnProperty.call(LEGACY_PLATFORM_MAPPINGS, platform) - ? LEGACY_PLATFORM_MAPPINGS[platform] - : [platform]; - - for (const replacement of replacements) { - if (!normalizedPlatforms.includes(replacement)) { - normalizedPlatforms.push(replacement); - } - } - } - - return { - changed: !arraysEqual(platforms, normalizedPlatforms), - normalizedPlatforms, - }; -} - -function nextModifiedTimestamp(existingModified) { - const now = Date.now(); - const existing = new Date(existingModified).getTime(); - const next = Number.isFinite(existing) ? Math.max(now, existing + 1) : now; - return new Date(next).toISOString(); -} - -function latestActiveDocumentsWithLegacyPlatformsPipeline() { - return [ - { $match: { 'stix.type': { $in: TARGET_TYPES } } }, - { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, - { $group: { _id: '$stix.id', document: { $first: '$$ROOT' } } }, - { $replaceRoot: { newRoot: '$document' } }, - { $match: { 'stix.x_mitre_deprecated': { $in: [null, false] } } }, - { $match: { 'stix.revoked': { $in: [null, false] } } }, - { $match: { 'stix.x_mitre_platforms': { $in: LEGACY_PLATFORMS } } }, - { $project: { _id: 0, __v: 0, __t: 0 } }, - ]; -} - -async function countRemainingLatestActiveDocumentsWithLegacyPlatforms(db) { - const [result] = await db - .collection('attackObjects') - .aggregate([...latestActiveDocumentsWithLegacyPlatformsPipeline(), { $count: 'remaining' }]) - .toArray(); - - return result?.remaining || 0; -} - -function ensureMongooseUsesClient(client) { - if (mongoose.connection.readyState === 0) { - mongoose.connection.setClient(client); - } -} - -function getServiceMap() { - const techniquesService = require('../app/services/stix/techniques-service'); - const softwareService = require('../app/services/stix/software-service'); - const dataSourcesService = require('../app/services/stix/data-sources-service'); - const assetsService = require('../app/services/stix/assets-service'); - const analyticsService = require('../app/services/stix/analytics-service'); - - return { - 'attack-pattern': techniquesService, - malware: softwareService, - tool: softwareService, - 'x-mitre-data-source': dataSourcesService, - 'x-mitre-asset': assetsService, - 'x-mitre-analytic': analyticsService, - }; -} - -async function prepareServiceLayer(client) { - ensureMongooseUsesClient(client); - - await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); - - const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); - if (!systemConfig?.organization_identity_ref) { - throw new Error( - 'System configuration is missing organization_identity_ref; cannot create new versions in platform normalization migration.', - ); - } -} - -module.exports = { - async up(db, client) { - const recorder = await createAutomationRunRecorder(db, { - automationType: 'migration', - name: MIGRATION_NAME, - trigger: { - source: 'startup', - runner: 'migrate-mongo', - }, - scope: { - collections: ['attackObjects'], - object_kinds: ['stix-object'], - target_types: TARGET_TYPES, - }, - metadata: { - legacy_platform_mappings: LEGACY_PLATFORM_MAPPINGS, - }, - }); - - const counts = { - scanned_candidates: 0, - attempted_reposts: 0, - updated: 0, - unchanged: 0, - failed: 0, - removed_platform_field: 0, - }; - const warnings = { - existing_validation_issues: 0, - }; - const failures = []; - let verification = {}; - - recorder.log('info', 'Starting migration', { - targetTypes: TARGET_TYPES, - legacyPlatforms: LEGACY_PLATFORMS, - }); - - try { - await prepareServiceLayer(client); - - const serviceMap = getServiceMap(); - const cursor = db - .collection('attackObjects') - .aggregate(latestActiveDocumentsWithLegacyPlatformsPipeline()); - - while (await cursor.hasNext()) { - const document = await cursor.next(); - counts.scanned_candidates++; - - const previousPlatforms = Array.isArray(document.stix?.x_mitre_platforms) - ? document.stix.x_mitre_platforms - : []; - const { changed, normalizedPlatforms } = normalizePlatforms(previousPlatforms); - - if (!changed) { - counts.unchanged++; - await recorder.recordItem({ - status: 'unchanged', - action: 'normalize_x_mitre_platforms', - target: { - kind: 'stix-object', - collection: 'attackObjects', - stix_id: document.stix?.id, - stix_type: document.stix?.type, - }, - details: { - previous_modified: document.stix?.modified, - changes: [ - { - field: 'stix.x_mitre_platforms', - before: previousPlatforms, - after: normalizedPlatforms, - }, - ], - }, - }); - continue; - } - - const service = serviceMap[document.stix?.type]; - if (!service) { - const errorMessage = `Unsupported STIX type for platform migration: ${document.stix?.type}`; - counts.failed++; - failures.push({ - stixId: document.stix?.id, - error: errorMessage, - }); - - await recorder.recordItem({ - status: 'failed', - action: 'normalize_x_mitre_platforms', - target: { - kind: 'stix-object', - collection: 'attackObjects', - stix_id: document.stix?.id, - stix_type: document.stix?.type, - }, - details: { - previous_modified: document.stix?.modified, - changes: [ - { - field: 'stix.x_mitre_platforms', - before: previousPlatforms, - after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, - }, - ], - }, - error: { message: errorMessage }, - }); - recorder.log('error', 'Unsupported STIX type encountered', { - stixId: document.stix?.id, - stixType: document.stix?.type, - }); - continue; - } - - const existingValidationErrorCount = document.workspace?.validation?.errors?.length || 0; - const itemWarnings = []; - if (existingValidationErrorCount > 0) { - warnings.existing_validation_issues++; - itemWarnings.push('existing_validation_issues'); - recorder.log('warn', 'Reposting object with existing validation issues', { - stixId: document.stix?.id, - validationErrorCount: existingValidationErrorCount, - }); - } - - const repost = JSON.parse(JSON.stringify(document)); - repost.stix.modified = nextModifiedTimestamp(document.stix?.modified); - counts.attempted_reposts++; - const removesPlatformField = normalizedPlatforms.length === 0; - - if (!removesPlatformField) { - repost.stix.x_mitre_platforms = normalizedPlatforms; - } else { - delete repost.stix.x_mitre_platforms; - } - - try { - const createdDocument = await service.create(repost, { - import: false, - automationContext: { - automationName: MIGRATION_NAME, - runId: recorder.runId, - }, - }); - - counts.updated++; - if (removesPlatformField) { - counts.removed_platform_field++; - } - - await recorder.recordItem({ - status: 'changed', - action: removesPlatformField ? 'remove_x_mitre_platforms' : 'normalize_x_mitre_platforms', - target: { - kind: 'stix-object', - collection: 'attackObjects', - stix_id: document.stix?.id, - stix_type: document.stix?.type, - }, - warnings: itemWarnings, - details: { - previous_modified: document.stix?.modified, - new_modified: createdDocument.stix?.modified || repost.stix.modified, - ...(existingValidationErrorCount > 0 && { - existing_validation_error_count: existingValidationErrorCount, - }), - changes: [ - { - field: 'stix.x_mitre_platforms', - before: previousPlatforms, - after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, - }, - ], - }, - }); - - recorder.log('info', 'Created normalized object version', { - stixId: document.stix?.id, - stixType: document.stix?.type, - previousModified: document.stix?.modified, - newModified: createdDocument.stix?.modified || repost.stix.modified, - before: previousPlatforms, - after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, - }); - } catch (error) { - counts.failed++; - failures.push({ - stixId: document.stix?.id, - error: error.message, - }); - - await recorder.recordItem({ - status: 'failed', - action: removesPlatformField ? 'remove_x_mitre_platforms' : 'normalize_x_mitre_platforms', - target: { - kind: 'stix-object', - collection: 'attackObjects', - stix_id: document.stix?.id, - stix_type: document.stix?.type, - }, - warnings: itemWarnings, - details: { - previous_modified: document.stix?.modified, - attempted_modified: repost.stix.modified, - ...(existingValidationErrorCount > 0 && { - existing_validation_error_count: existingValidationErrorCount, - }), - changes: [ - { - field: 'stix.x_mitre_platforms', - before: previousPlatforms, - after: normalizedPlatforms.length > 0 ? normalizedPlatforms : null, - }, - ], - }, - error: serializeError(error), - }); - - recorder.log('error', 'Failed to create normalized object version', { - stixId: document.stix?.id, - stixType: document.stix?.type, - previousModified: document.stix?.modified, - attemptedModified: repost.stix.modified, - error: error.message, - }); - } - } - - verification = { - remaining_latest_active_objects_with_legacy_platforms: - await countRemainingLatestActiveDocumentsWithLegacyPlatforms(db), - }; - - if (failures.length > 0) { - throw new Error( - `Platform normalization migration failed for ${failures.length} object(s); ` + - `see automationRuns/automationRunItems for details.`, - ); - } - - const summary = { - message: - `Normalized x_mitre_platforms on ${counts.updated} active latest object(s) ` + - `after scanning ${counts.scanned_candidates} candidate(s); removed the field entirely on ` + - `${counts.removed_platform_field} object(s).`, - }; - - await recorder.finish({ - status: 'completed', - counts, - warnings, - verification, - summary, - errorSummary: null, - }); - - recorder.log('info', summary.message, { - counts, - warnings, - verification, - }); - } catch (error) { - verification = { - ...verification, - remaining_latest_active_objects_with_legacy_platforms: - verification.remaining_latest_active_objects_with_legacy_platforms ?? - (await countRemainingLatestActiveDocumentsWithLegacyPlatforms(db).catch(() => null)), - }; - - const status = counts.updated > 0 ? 'partial' : 'failed'; - - await recorder.finish({ - status, - counts, - warnings, - verification, - summary: { - message: 'Platform normalization migration did not complete successfully.', - }, - errorSummary: serializeError(error), - }); - - recorder.log('error', 'Migration failed', { - counts, - warnings, - verification, - error: error.message, - }); - - throw error; - } - }, - - async down() { - // No safe automatic rollback: the up migration creates new historical - // versions via the normal application workflow. - logger.info( - `[${MIGRATION_NAME}] down migration is a no-op: created replacement versions are retained as part of object history`, - ); - }, -}; From c134083d4a63a4539eb33c29e335d638e6ff8725 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 11 May 2026 17:00:41 -0400 Subject: [PATCH 331/370] fix(platforms): restore Network and restrict ICS selection - upgrade ADM to v4.11.6 to permit Network in x_mitre_platforms - change Network to Network Devices for ICS Assets in allowed-values.json --- app/config/allowed-values.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/config/allowed-values.json b/app/config/allowed-values.json index 96736e93..22bb412c 100644 --- a/app/config/allowed-values.json +++ b/app/config/allowed-values.json @@ -303,7 +303,7 @@ "domains": [ { "domainName": "ics-attack", - "allowedValues": ["Windows", "Linux", "Network Devices", "Embedded", "IaaS", "SaaS"] + "allowedValues": ["Windows", "Linux", "Network", "Embedded", "IaaS", "SaaS"] } ] } diff --git a/package-lock.json b/package-lock.json index 9b40fbd4..f8b0db20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.5", + "@mitre-attack/attack-data-model": "^4.11.6", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -878,9 +878,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.11.5", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.5.tgz", - "integrity": "sha512-z2plVSQjK6M8wzqBaV43ln0IxjJQJii0MgpK4LNUHqlFRicTRoFNPR53QLXlT/d9jUEWO54RN09Yo9DX+/I2gA==", + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.6.tgz", + "integrity": "sha512-zJF/r6lZHUllDn0PRmlSj/6v2I7YGtTImJf1cJUmOh21L9mC0usTHBmRNMJUcIIwJFqlVxkyfMwf6lEPzVNksw==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index f2bd37c3..4ddf6cbd 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.5", + "@mitre-attack/attack-data-model": "^4.11.6", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", From 6223705143c42b74b38344bc989a7dc6b5f4ee0d Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 12 May 2026 10:58:12 -0400 Subject: [PATCH 332/370] fix(validation): upgrade ADM to v4.11.7 detection-strategies: make x_mitre_contributors optional --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8b0db20..12003c86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.6", + "@mitre-attack/attack-data-model": "^4.11.7", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", @@ -878,9 +878,9 @@ "license": "MIT" }, "node_modules/@mitre-attack/attack-data-model": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.6.tgz", - "integrity": "sha512-zJF/r6lZHUllDn0PRmlSj/6v2I7YGtTImJf1cJUmOh21L9mC0usTHBmRNMJUcIIwJFqlVxkyfMwf6lEPzVNksw==", + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-4.11.7.tgz", + "integrity": "sha512-UK9Wyxy1FT465v5aNXU1EBCphUBaiGTERax9zeTBa2ETZZq731EZ+cVVYa9VHLq3JGeOpgjmYMc9LbtEn8XDhg==", "license": "APACHE-2.0", "dependencies": { "axios": "^1.9.0", diff --git a/package.json b/package.json index 4ddf6cbd..da48cfd1 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^13.0.1", - "@mitre-attack/attack-data-model": "^4.11.6", + "@mitre-attack/attack-data-model": "^4.11.7", "async": "^3.2.6", "async-await-retry": "^2.1.0", "body-parser": "^2.2.1", From c091f567fa43eac57f46522536160c7eb7f0b056 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Tue, 12 May 2026 14:04:33 -0400 Subject: [PATCH 333/370] fix(migrations): skip org-identity check when no candidates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two latest-version repost migrations (mutable-elements deduplication and noncompliant-related-assets demotion) asserted that system_configuration.organization_identity_ref was set before scanning for candidate documents. On fresh deployments this aborted app startup, because checkSystemConfiguration() — which creates the placeholder organization identity — runs *after* migrate-mongo in bin/www. Count candidates first and finish the automation run as a clean no-op when there is nothing to repost. The organization-identity assertion now only fires when the migration actually needs to create new versions, so fresh DBs proceed through startup and the placeholder identity is created normally on the post-migration system-configuration check. --- ...0000-normalize-x-mitre-mutable-elements.js | 34 +++++++++++++++++++ ...emote-noncompliant-asset-related-assets.js | 32 +++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/migrations/20260511130000-normalize-x-mitre-mutable-elements.js b/migrations/20260511130000-normalize-x-mitre-mutable-elements.js index f5acabb9..8bb76d00 100644 --- a/migrations/20260511130000-normalize-x-mitre-mutable-elements.js +++ b/migrations/20260511130000-normalize-x-mitre-mutable-elements.js @@ -143,7 +143,9 @@ async function prepareServiceLayer(client) { ensureMongooseUsesClient(client); await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); +} +async function assertOrganizationIdentityConfigured() { const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); if (!systemConfig?.organization_identity_ref) { throw new Error( @@ -192,6 +194,38 @@ module.exports = { try { await prepareServiceLayer(client); + const candidateCount = + await countRemainingLatestActiveAnalyticsWithDuplicateMutableElements(db); + if (candidateCount === 0) { + verification = { + remaining_latest_active_analytics_with_duplicate_mutable_elements: 0, + }; + + const summary = { + message: + 'No active latest analytic(s) require mutable-elements deduplication; migration is a no-op.', + }; + + await recorder.finish({ + status: 'completed', + counts, + warnings, + verification, + summary, + errorSummary: null, + }); + + recorder.log('info', summary.message, { + counts, + warnings, + verification, + }); + + return; + } + + await assertOrganizationIdentityConfigured(); + const analyticsService = require('../app/services/stix/analytics-service'); const cursor = db diff --git a/migrations/20260511140000-demote-noncompliant-asset-related-assets.js b/migrations/20260511140000-demote-noncompliant-asset-related-assets.js index 20cc612f..3f3cea54 100644 --- a/migrations/20260511140000-demote-noncompliant-asset-related-assets.js +++ b/migrations/20260511140000-demote-noncompliant-asset-related-assets.js @@ -145,7 +145,9 @@ async function prepareServiceLayer(client) { ensureMongooseUsesClient(client); await validationBypassesService.loadStaticRules(config.configurationFiles.staticBypassRulesPath); +} +async function assertOrganizationIdentityConfigured() { const systemConfig = await systemConfigurationRepository.retrieveOne({ lean: true }); if (!systemConfig?.organization_identity_ref) { throw new Error( @@ -197,6 +199,36 @@ module.exports = { try { await prepareServiceLayer(client); + const candidateCount = await countRemainingNoncompliantReviewedAssets(db); + if (candidateCount === 0) { + verification = { + remaining_latest_active_reviewed_assets_with_noncompliant_related_assets: 0, + }; + + const summary = { + message: 'No active latest reviewed asset(s) require demotion; migration is a no-op.', + }; + + await recorder.finish({ + status: 'completed', + counts, + warnings, + verification, + summary, + errorSummary: null, + }); + + recorder.log('info', summary.message, { + counts, + warnings, + verification, + }); + + return; + } + + await assertOrganizationIdentityConfigured(); + const assetsService = require('../app/services/stix/assets-service'); const cursor = db From 1ba2148d5169ed9488452968e805e1e7becf0b6b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 11:06:14 -0400 Subject: [PATCH 334/370] perf(import-bundle): batch and parallelize STIX bundle import Replaces the per-object sequential loop with a tier-batched pipeline. Objects are grouped by STIX type (preserving the existing dependency order); each tier pre-fetches existing versions in one $in query, composes and validates in parallel with bounded concurrency, persists via a single insertMany(ordered:false), and runs afterCreate + emitCreatedEvent in a second parallel pass. Adds repository.retrieveAllByStixIds and repository.saveMany, factors composeForImport out of _createFromImport so it can be batched, and memoizes locally-derived .partial() Zod schemas for WIP objects. Cuts Enterprise bundle import from 5+ minutes (reverse-proxy timeout territory) to well under a minute on developer hardware. --- app/lib/validation-schemas.js | 17 +- app/repository/_base.repository.js | 77 ++++ app/services/meta-classes/base.service.js | 91 ++-- .../import-bundle.js | 394 ++++++++++++------ 4 files changed, 427 insertions(+), 152 deletions(-) diff --git a/app/lib/validation-schemas.js b/app/lib/validation-schemas.js index ede9f767..c68f6a4b 100644 --- a/app/lib/validation-schemas.js +++ b/app/lib/validation-schemas.js @@ -91,6 +91,12 @@ const STIX_SCHEMAS = { 'x-mitre-collection': collectionSchema, }; +// Cache for locally-derived partial schemas. ADM does not export prebuilt +// partials for every STIX type; for those types we call `.partial()` ourselves. +// That call is expensive enough to show up in bulk-import profiles, so we +// memoize the result per STIX type. +const derivedPartialCache = new Map(); + /** * Get the schema to use for validating a STIX object. * @@ -102,7 +108,7 @@ const STIX_SCHEMAS = { * - `work-in-progress` uses partial validation so drafts can omit required fields * - every other workflow state uses full validation * - if ADM exports a dedicated partial schema, use it directly - * - otherwise, derive a partial schema locally with `.partial()` + * - otherwise, derive a partial schema locally with `.partial()` (memoized) * * @param {string} stixType - The STIX `type` being validated (e.g. "attack-pattern") * @param {string} status - The workflow state (e.g. "work-in-progress", "awaiting-review", "reviewed") @@ -120,7 +126,14 @@ function getSchema(stixType, status) { return isWip ? admSchemaRef.partial : admSchemaRef.full; } - return isWip ? admSchemaRef.partial() : admSchemaRef; + if (!isWip) return admSchemaRef; + + let derived = derivedPartialCache.get(stixType); + if (!derived) { + derived = admSchemaRef.partial(); + derivedPartialCache.set(stixType, derived); + } + return derived; } module.exports = { diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index 9a3443eb..85f80f04 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -399,6 +399,83 @@ class BaseRepository extends AbstractRepository { } } + /** + * Bulk insert. Used by the STIX bundle import path to avoid one round-trip + * per object. `ordered: false` keeps MongoDB inserting the remaining docs + * after an individual failure; per-doc errors are returned on the thrown + * BulkWriteError's `writeErrors` for the caller to fold into import errors. + * + * Discriminator-aware: each child model's `insertMany` sets the correct + * `__t` discriminator key automatically, so callers should invoke this on + * the type-specific repository (not the AttackObject parent). + * + * @param {Array} dataArr - Array of plain objects to insert + * @param {Object} [options] + * @param {boolean} [options.ordered=false] - Stop on first error if true + * @returns {Promise<{ inserted: Array, errors: Array<{ index, message, code }> }>} + */ + async saveMany(dataArr, { ordered = false } = {}) { + if (!Array.isArray(dataArr) || dataArr.length === 0) { + return { inserted: [], errors: [] }; + } + try { + const inserted = await this.model.insertMany(dataArr, { ordered }); + return { inserted, errors: [] }; + } catch (err) { + // Mongoose BulkWriteError surfaces partial success when ordered:false. + // Successful inserts are on err.insertedDocs; failures on err.writeErrors. + if (err?.name === 'MongoBulkWriteError' || err?.writeErrors) { + const errors = (err.writeErrors || []).map((we) => ({ + index: we.index ?? we.err?.index, + message: we.errmsg || we.err?.errmsg || we.message, + code: we.code || we.err?.code, + })); + return { inserted: err.insertedDocs || [], errors }; + } + throw new DatabaseError(err); + } + } + + /** + * Retrieve every version of every document whose `stix.id` is in `stixIds`. + * Returns a Map keyed by stixId, value is an array of versions sorted + * newest-first (matching `retrieveAllById`'s ordering). + * + * Used by the bundle-import path to pre-fetch all existing versions in one + * query instead of N queries (one per imported object). + * + * @param {Array} stixIds - List of STIX IDs to look up + * @returns {Promise>>} + */ + async retrieveAllByStixIds(stixIds) { + if (!Array.isArray(stixIds) || stixIds.length === 0) { + return new Map(); + } + + try { + const documents = await this.model + .find({ 'stix.id': { $in: stixIds } }) + .sort('-stix.modified') + .select('-_id -__v -__t') + .lean() + .exec(); + + const byStixId = new Map(); + for (const doc of documents) { + const id = doc.stix.id; + let arr = byStixId.get(id); + if (!arr) { + arr = []; + byStixId.set(id, arr); + } + arr.push(doc); + } + return byStixId; + } catch (err) { + throw new DatabaseError(err); + } + } + async updateAndSave(document, data) { try { // TODO validate that document is valid mongoose object first diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 93e86bf3..c2128bc3 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -688,6 +688,46 @@ class BaseService extends ServiceWithHooks { * @private */ async _createFromImport(data, options) { + const { + data: composed, + warnings, + throwIfValidating, + } = await this.composeForImport(data, options); + + if (throwIfValidating) throw throwIfValidating; + + if (options.dryRun) { + return { ...composed, warnings }; + } + + await this.beforeCreate(composed, options); + const createdDocument = await this.repository.save(composed); + await this.afterCreate(createdDocument, options); + await this.emitCreatedEvent(createdDocument, options); + + const result = createdDocument.toObject ? createdDocument.toObject() : createdDocument; + result.warnings = warnings; + return result; + } + + /** + * Compose and validate an object for import — no I/O, no events. + * + * Stamps `workspace.attack_id` from the bundle's ATT&CK external reference, + * runs ADM validation (unless the object is revoked/deprecated), and + * applies fail-open semantics by attaching `workspace.validation` when + * errors are found and `options.validateContents` is not set. + * + * The result is a plain object ready to hand to `repository.save()` or + * `repository.saveMany()`. The bundle-import path uses this directly so it + * can batch persistence; the single-object import path wraps it in + * `_createFromImport` to keep lifecycle hooks and event emission. + * + * @param {Object} data - The request data ({ stix, workspace }) + * @param {Object} options - Options passed from create() + * @returns {Promise<{ data: Object, warnings: Array, throwIfValidating: ValidationError|null }>} + */ + async composeForImport(data, options) { // Strip workspace.validation — server-controlled; the fail-open block // below is the only legitimate writer of this field on the import path. if (data.workspace) { @@ -714,40 +754,33 @@ class BaseService extends ServiceWithHooks { ({ errors, warnings } = await this.validateComposedObject(data)); } + let throwIfValidating = null; if (errors.length > 0) { if (options.validateContents) { - throw new ValidationError('ADM validation failed', { details: errors, warnings }); + throwIfValidating = new ValidationError('ADM validation failed', { + details: errors, + warnings, + }); + } else { + // Fail-open: store validation errors on the document + const { ATTACK_SPEC_VERSION } = require('@mitre-attack/attack-data-model'); + const admPkg = require('@mitre-attack/attack-data-model/package.json'); + + data.workspace = data.workspace || {}; + data.workspace.validation = { + errors: errors.map((e) => ({ message: e.message, path: e.path, code: e.code })), + attack_spec_version: ATTACK_SPEC_VERSION, + adm_version: admPkg.version, + validated_at: new Date(), + }; + + logger.warn( + `Import: ${data.stix.id} has ${errors.length} validation error(s), storing on document`, + ); } - - // Fail-open: store validation errors on the document - const { ATTACK_SPEC_VERSION } = require('@mitre-attack/attack-data-model'); - const admPkg = require('@mitre-attack/attack-data-model/package.json'); - - data.workspace = data.workspace || {}; - data.workspace.validation = { - errors: errors.map((e) => ({ message: e.message, path: e.path, code: e.code })), - attack_spec_version: ATTACK_SPEC_VERSION, - adm_version: admPkg.version, - validated_at: new Date(), - }; - - logger.warn( - `Import: ${data.stix.id} has ${errors.length} validation error(s), storing on document`, - ); - } - - if (options.dryRun) { - return { ...data, warnings }; } - await this.beforeCreate(data, options); - const createdDocument = await this.repository.save(data); - await this.afterCreate(createdDocument, options); - await this.emitCreatedEvent(createdDocument, options); - - const result = createdDocument.toObject ? createdDocument.toObject() : createdDocument; - result.warnings = warnings; - return result; + return { data, warnings, throwIfValidating }; } /** diff --git a/app/services/stix/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js index 02b55f5e..af6b06f1 100644 --- a/app/services/stix/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -11,12 +11,36 @@ const { defaultAttackSpecVersion, toEpoch, } = require('./bundle-helpers'); -const { DuplicateIdError } = require('../../../exceptions'); const logger = require('../../../lib/logger'); const config = require('../../../config/config'); const types = require('../../../lib/types'); +// Bounded concurrency for the compose-and-validate phase. Each task runs Zod +// validation and a small amount of synchronous work, so we cap concurrency +// to avoid pinning the event loop on extremely large bundles. +const COMPOSE_CONCURRENCY = 25; + +/** + * Run `task` against every item in `items` with at most `limit` in flight. + * Inline replacement for p-limit so we don't pull a new dependency (and + * avoid the ESM-only issue in recent p-limit versions). + */ +async function runWithConcurrency(items, limit, task) { + let next = 0; + async function worker() { + while (true) { + const i = next++; + if (i >= items.length) return; + await task(items[i], i); + } + } + const workerCount = Math.min(limit, items.length); + const workers = []; + for (let i = 0; i < workerCount; i++) workers.push(worker()); + await Promise.all(workers); +} + const collectionsService = require('../collections-service'); const referencesService = require('../../system/references-service'); @@ -151,109 +175,260 @@ function checkIfAlias(importObject, sourceName) { } /** - * Process a single STIX object during bundle import - * @param {Object} importObject - The STIX object to process - * @param {Object} options - Import options + * Records an unknown-object-type error against the imported collection. + * @param {Object} importObject - The unknown STIX object * @param {Object} importedCollection - Collection being imported - * @param {Object} collectionReference - Reference to the collection - * @param {Map} importReferences - Map of references being imported - * @param {Object} referenceImportResults - Tracking of reference import stats - * @returns {Promise} Resolves when object is processed */ -async function processStixObject( - importObject, - options, - importedCollection, - collectionReference, - importReferences, - referenceImportResults, -) { - const service = getServiceForType(importObject.type); +function recordUnknownTypeError(importObject, importedCollection) { + const importError = { + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.unknownObjectType, + error_message: `Unknown object type: ${importObject.type}`, + }; + logger.verbose( + `Import Bundle Error: Unknown object type. id=${importObject.id}, modified=${importObject.modified}, type=${importObject.type}`, + ); + importedCollection.workspace.import_categories.errors.push(importError); +} - if (!service) { - if (importObject.type === types.Collection) { - return; // Skip x-mitre-collection objects +/** + * Process one tier of same-type STIX objects: contents-map check, spec-version + * gate, bulk pre-fetch of existing versions, parallel compose-and-validate, + * then a single bulk insert. + * + * Tier-based grouping is sound because `sortObjectsByDependencies` returns a + * stable sort that keeps every type together — and types appear in dependency + * order (data-source before data-component, etc.). Each tier persists fully + * before the next tier begins. + * + * @param {string} type - STIX type for this tier + * @param {Array} objects - STIX objects of this type + * @param {Object} ctx - Shared import context + */ +async function processTier(type, objects, ctx) { + const { + options, + importedCollection, + contentsMap, + collectionReference, + importReferences, + referenceImportResults, + } = ctx; + + // Filter the tier: drain contents-map, gate on ATT&CK spec version, and + // record per-object errors. The result is the set of objects eligible for + // compose-and-insert. + const eligible = []; + for (const importObject of objects) { + if ( + !contentsMap.delete(makeKeyFromObject(importObject)) && + importObject.type !== types.Collection + ) { + const importError = { + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.notInContents, + error_message: + 'Warning: Object in bundle but not in x_mitre_contents. Object will be saved in database.', + }; + logger.verbose( + `Import Bundle Warning: Object not in x_mitre_contents. id=${importObject.id}, modified=${importObject.modified}`, + ); + importedCollection.workspace.import_categories.errors.push(importError); } - // Record error for unknown type but continue import - const importError = { - object_ref: importObject.id, - object_modified: importObject.modified, - error_type: importErrors.unknownObjectType, - error_message: `Unknown object type: ${importObject.type}`, - }; - logger.verbose( - `Import Bundle Error: Unknown object type. id=${importObject.id}, modified=${importObject.modified}, type=${importObject.type}`, - ); - importedCollection.workspace.import_categories.errors.push(importError); + if (importObject.type !== 'marking-definition') { + const objectAttackSpecVersion = + importObject.x_mitre_attack_spec_version ?? defaultAttackSpecVersion; + if (semver.gt(objectAttackSpecVersion, config.app.attackSpecVersion)) { + const importError = { + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.attackSpecVersionViolation, + error_message: 'Error: Object x_mitre_attack_spec_version later than system.', + }; + logger.verbose( + `Import Bundle Error: Object's x_mitre_attack_spec_version later than system. id=${importObject.id}, modified=${importObject.modified}`, + ); + importedCollection.workspace.import_categories.errors.push(importError); + + if ( + !options.forceImportParameters?.includes( + forceImportParameters.attackSpecVersionViolations, + ) + ) { + throw new Error(errors.attackSpecVersionViolation); + } + continue; + } + } + eligible.push(importObject); + } + + const service = getServiceForType(type); + + // Unknown / unsupported types: record per-object errors but continue the import. + // Collection objects (the bundle itself) are deliberately skipped. + if (!service) { + if (type === types.Collection) return; + for (const importObject of eligible) { + recordUnknownTypeError(importObject, importedCollection); + } return; } + // Pre-fetch every existing version of every stixId in this tier in ONE query. + // Replaces N calls to service.retrieveById from the old per-object loop. + let existingByStixId; try { - // Retrieve existing objects with same STIX ID - const objects = await service.retrieveById(importObject.id, { versions: 'all' }); + const ids = eligible.map((o) => o.id); + existingByStixId = await service.repository.retrieveAllByStixIds(ids); + } catch (err) { + logger.error(err); + for (const importObject of eligible) { + const importError = { + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.retrievalError, + }; + logger.verbose( + `Import Bundle Error: Unable to retrieve objects with matching STIX id. id=${importObject.id}, modified=${importObject.modified}`, + ); + importedCollection.workspace.import_categories.errors.push(importError); + } + return; + } - // Check for duplicate object - const isDuplicate = checkForDuplicate(importObject, objects); - if (isDuplicate) { + // Compose-and-validate in parallel with bounded concurrency. Each task runs + // the duplicate check, categorization, external-references collection, + // Zod validation via `composeForImport`, and the service's `beforeCreate` + // hook (which populates outbound `workspace.embedded_relationships` on the + // doc being saved). Composed docs are accumulated into a single array for + // bulk insert. + const composedToInsert = []; + const composeOptions = { + import: true, + validateContents: options.validateContents, + }; + + await runWithConcurrency(eligible, COMPOSE_CONCURRENCY, async (importObject) => { + const existing = existingByStixId.get(importObject.id) || []; + + if (checkForDuplicate(importObject, existing)) { importedCollection.workspace.import_categories.duplicates.push(importObject.id); return; } - // Categorize the object (addition, change, etc) - categorizeObject(importObject, objects, importedCollection); - - // Process external references + categorizeObject(importObject, existing, importedCollection); processExternalReferences(importObject, importReferences, referenceImportResults); - // Save the object if not preview mode - if (!options.previewOnly) { - const newObject = { - workspace: { - collections: [collectionReference], - }, - stix: importObject, - }; + if (options.previewOnly) return; + + const stagingDoc = { + workspace: { + collections: [collectionReference], + }, + stix: importObject, + }; + + try { + const { data: composed, throwIfValidating } = await service.composeForImport( + stagingDoc, + composeOptions, + ); + if (throwIfValidating) { + const importError = { + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.saveError, + error_message: throwIfValidating.message, + }; + logger.verbose( + `Import Bundle Error: Validation failed. id=${importObject.id}, ${throwIfValidating.message}`, + ); + importedCollection.workspace.import_categories.errors.push(importError); + return; + } + + // Run the service's beforeCreate hook so outbound embedded_relationships + // and any other pre-persist data shaping are present on the doc when + // saveMany writes it. Failures here are recorded as save errors and the + // doc is dropped from the bulk insert. try { - // TODO should we bypass validation for imports? - // or possibly fail open on validation errors where we record the validation error on the object but still allow the import to proceed? - // for validation errors, the object may need to be placed into a quarantined state where it is visible but read-only except through a PUT operation that allows updates to be made to fix the validation errors - await service.create(newObject, { - import: true, - validateContents: options.validateContents, - }); - } catch (err) { - if (err.message === service.errors?.duplicateId || err instanceof DuplicateIdError) { - throw err; - } - // Record save error but continue import + await service.beforeCreate(composed, composeOptions); + } catch (hookErr) { const importError = { object_ref: importObject.id, object_modified: importObject.modified, error_type: importErrors.saveError, - error_message: err.message, + error_message: hookErr.message, }; logger.verbose( - `Import Bundle Error: Unable to save object. id=${importObject.id}, modified=${importObject.modified}, ${err.message}`, + `Import Bundle Error: beforeCreate hook failed. id=${importObject.id}, ${hookErr.message}`, ); importedCollection.workspace.import_categories.errors.push(importError); + return; } + + composedToInsert.push(composed); + } catch (err) { + const importError = { + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.saveError, + error_message: err.message, + }; + logger.verbose( + `Import Bundle Error: Unable to compose object. id=${importObject.id}, modified=${importObject.modified}, ${err.message}`, + ); + importedCollection.workspace.import_categories.errors.push(importError); } - } catch (err) { - logger.error(err); + }); + + if (composedToInsert.length === 0) return; - // Record retrieval error but continue import + // Bulk insert. `saveMany` uses MongoDB `insertMany` with `ordered:false`, + // so individual document failures (e.g., duplicate-id races) are returned + // per-doc and folded into the import errors below — they don't abort the + // remaining inserts. + const { inserted, errors: insertErrors } = await service.repository.saveMany(composedToInsert); + for (const wErr of insertErrors) { + const failedDoc = typeof wErr.index === 'number' ? composedToInsert[wErr.index] : undefined; const importError = { - object_ref: importObject.id, - object_modified: importObject.modified, - error_type: importErrors.retrievalError, + object_ref: failedDoc?.stix?.id, + object_modified: failedDoc?.stix?.modified, + error_type: importErrors.saveError, + error_message: wErr.message, }; logger.verbose( - `Import Bundle Error: Unable to retrieve objects with matching STIX id. id=${importObject.id}, modified=${importObject.modified}`, + `Import Bundle Error: Unable to save object. id=${importError.object_ref}, modified=${importError.object_modified}, ${wErr.message}`, ); importedCollection.workspace.import_categories.errors.push(importError); } + + // Post-insert lifecycle: run `afterCreate` and emit the `{type}::created` + // event for each successfully inserted doc. These fire cross-service domain + // events that maintain INBOUND `workspace.embedded_relationships` on + // referenced documents (e.g., DetectionStrategy → Analytic, Analytic → + // DataComponent, DataComponent → DataSource). Skipping them would leave + // the frontend unable to navigate inbound relationships. + // + // Run in parallel with bounded concurrency; per-doc hook failures are + // logged but never abort the import. + await runWithConcurrency(inserted, COMPOSE_CONCURRENCY, async (doc) => { + try { + await service.afterCreate(doc, composeOptions); + } catch (err) { + logger.warn(`Import Bundle: afterCreate failed for ${doc?.stix?.id}: ${err.message}`); + } + try { + await service.emitCreatedEvent(doc, composeOptions); + } catch (err) { + logger.warn(`Import Bundle: emitCreatedEvent failed for ${doc?.stix?.id}: ${err.message}`); + } + }); } /** @@ -296,7 +471,14 @@ function sortObjectsByDependencies(objects) { } /** - * Process all objects in the bundle + * Process all objects in the bundle, batched by STIX type in dependency order. + * + * Each type tier runs sequentially (so e.g. data-sources finish before + * data-components start), but objects within a tier are composed in parallel + * and persisted with a single bulk `insertMany`. This replaces the previous + * per-object sequential loop that did a DB read + DB write + lifecycle hooks + * + event emission per imported object — the dominant cost in large bundles. + * * @param {Array} objects - Array of STIX objects to process * @param {Object} options - Import options * @param {Object} importedCollection - Collection being imported @@ -314,62 +496,32 @@ async function processObjects( importReferences, referenceImportResults, ) { - // Sort objects by dependencies before processing const sortedObjects = sortObjectsByDependencies(objects); - for (const importObject of sortedObjects) { - // Check if object is in x_mitre_contents - if ( - !contentsMap.delete(makeKeyFromObject(importObject)) && - importObject.type !== types.Collection - ) { - const importError = { - object_ref: importObject.id, - object_modified: importObject.modified, - error_type: importErrors.notInContents, - error_message: - 'Warning: Object in bundle but not in x_mitre_contents. Object will be saved in database.', - }; - logger.verbose( - `Import Bundle Warning: Object not in x_mitre_contents. id=${importObject.id}, modified=${importObject.modified}`, - ); - importedCollection.workspace.import_categories.errors.push(importError); + // Group consecutive same-type objects into tiers. The sort above places + // every type contiguously and in dependency order, so a single pass over + // the sorted list is enough. + const tiers = []; + let currentTier = null; + for (const obj of sortedObjects) { + if (!currentTier || currentTier.type !== obj.type) { + currentTier = { type: obj.type, objects: [] }; + tiers.push(currentTier); } + currentTier.objects.push(obj); + } - if (importObject.type != 'marking-definition') { - // Check ATT&CK Spec Version compatibility - const objectAttackSpecVersion = - importObject.x_mitre_attack_spec_version ?? defaultAttackSpecVersion; - if (semver.gt(objectAttackSpecVersion, config.app.attackSpecVersion)) { - const importError = { - object_ref: importObject.id, - object_modified: importObject.modified, - error_type: importErrors.attackSpecVersionViolation, - error_message: 'Error: Object x_mitre_attack_spec_version later than system.', - }; - logger.verbose( - `Import Bundle Error: Object's x_mitre_attack_spec_version later than system. id=${importObject.id}, modified=${importObject.modified}`, - ); - importedCollection.workspace.import_categories.errors.push(importError); + const ctx = { + options, + importedCollection, + contentsMap, + collectionReference, + importReferences, + referenceImportResults, + }; - if ( - !options.forceImportParameters?.includes( - forceImportParameters.attackSpecVersionViolations, - ) - ) { - throw new Error(errors.attackSpecVersionViolation); - } - continue; - } - } - await processStixObject( - importObject, - options, - importedCollection, - collectionReference, - importReferences, - referenceImportResults, - ); + for (const tier of tiers) { + await processTier(tier.type, tier.objects, ctx); } // Check for objects in x_mitre_contents but not in bundle From b12ced62575caff99863f5bab30f519373145f2a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 11:06:49 -0400 Subject: [PATCH 335/370] feat(config): enable ADM validation by default Flips VALIDATE_WITH_ADM_SCHEMAS to default true. Deployments that did not explicitly enable it previously ran no ADM validation at all on POST, PUT, or bundle import, silently masking malformed content. Deployments that want to keep validation off can still set VALIDATE_WITH_ADM_SCHEMAS=false explicitly. --- app/config/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/config.js b/app/config/config.js index d718617a..74383761 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -205,7 +205,7 @@ function loadConfig() { withAttackDataModel: { doc: 'Enable validation of POST and PUT request bodies using the ATT&CK Data Model', format: Boolean, - default: false, + default: true, env: 'VALIDATE_WITH_ADM_SCHEMAS', }, withOpenApi: { From eea4f1e2c89915b3fe7f58b1f6cdbe60654d3907 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 11:08:36 -0400 Subject: [PATCH 336/370] feat(import-bundle): enforce stix-fidelity contract on lifecycle hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an import-fidelity contract that prevents lifecycle hooks and event listeners from deviating bundle `stix.*` content during a STIX bundle import. Workspace mutations remain permitted; only stix is frozen. New app/lib/import-safety.js exports deepFreezeStix(doc), which recursively freezes stix (including nested arrays and their elements). The framework calls it before invoking any hook or listener in import mode — in BaseService._createFromImport and in the bulk import pipeline. With stix frozen, a missing or misplaced `if (!options.import)` gate fails closed with a TypeError pointing at the violating line on the first import test, rather than silently rewriting bundle content. Five hooks/listeners that legitimately mutate stix.* on user-driven flows are gated for import: analytics-service.beforeCreate (the stix.name stamp), campaigns/groups/software.beforeCreate (alias normalization and the malware is_family default), and the analytics-service.handleAnalyticsReferenced listener (the external_references URL rewrite). The three afterCreate emit sites that drive metadata cascades now forward `options` in their event payloads so listeners can see when an import is in progress. --- app/lib/import-safety.js | 86 +++++++++++++++++++ app/services/meta-classes/base.service.js | 16 ++++ app/services/stix/analytics-service.js | 83 ++++++++++++------ app/services/stix/campaigns-service.js | 13 ++- .../import-bundle.js | 14 +++ app/services/stix/data-components-service.js | 11 ++- .../stix/detection-strategies-service.js | 11 ++- app/services/stix/groups-service.js | 12 ++- app/services/stix/software-service.js | 15 +++- 9 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 app/lib/import-safety.js diff --git a/app/lib/import-safety.js b/app/lib/import-safety.js new file mode 100644 index 00000000..ab29e2b0 --- /dev/null +++ b/app/lib/import-safety.js @@ -0,0 +1,86 @@ +'use strict'; + +/** + * Import-safety primitives. + * + * STIX-bundle import has a strict invariant: when a bundle is imported, the + * persisted objects must be byte-faithful to the bundle's `stix` content. The + * import path is allowed to populate Workbench-private metadata (everything + * under `workspace`), but it must NEVER alter the imported `stix` fields, + * because the bundle is the source of truth and round-trip fidelity matters + * for re-imports and downstream consumers. + * + * However, the lifecycle hooks and event listeners that fire during a normal + * create/update — `beforeCreate`, `afterCreate`, and the cross-service + * listeners on domain events — were not originally written with import + * fidelity in mind. Several of them mutate `stix.*` as part of their normal + * work (e.g. AnalyticsService.beforeCreate stamps `stix.name` from the + * ATT&CK ID; AnalyticsService.handleAnalyticsReferenced rewrites + * `stix.external_references` to embed a URL to the parent detection + * strategy). Those mutations are correct for user-driven POST/PUT flows but + * are incorrect for an import. + * + * Rather than rely on convention ("remember to gate every stix write behind + * `if (!options.import) { ... }`"), we enforce the contract structurally: + * before invoking any hook or listener in import mode, the framework calls + * `deepFreezeStix(doc)`. In Node strict mode (`'use strict'` at the top of + * every service file), an attempted assignment to a frozen property throws + * a `TypeError`. That makes a missing import gate fail loudly at the + * violating line on the first import test, instead of silently corrupting + * bundle content. + * + * The local rule for hook/listener authors becomes: + * + * 1. Workspace mutations are always allowed. + * 2. If you need to mutate `stix.*`, wrap the block in + * `if (!options.import) { ... }` (or `if (!payload.options?.import)` + * inside a listener). The framework guarantees that `options.import` + * is the only state where stix is frozen, so a missing gate produces + * an immediate TypeError pointing at the offending line. + * + * Read freely from frozen stix — only writes are blocked. + */ + +/** + * Deep-freezes the `stix` field of a document so any attempt to write to + * `doc.stix.*`, including writes through nested arrays/objects (e.g. + * `doc.stix.external_references.unshift(...)`), throws a `TypeError`. + * + * `Object.freeze` is shallow on its own, so we walk the immediate children + * (one level into nested objects/arrays) and freeze them as well. Two + * levels is sufficient for STIX in practice: the deepest commonly mutated + * paths are array elements (e.g. `stix.external_references[i]` or + * `stix.kill_chain_phases[i]`), which the loop covers. + * + * Safe to call multiple times — `Object.isFrozen` short-circuits. + * Safe to call on Mongoose documents: only the underlying `_doc.stix` + * subtree is frozen; Mongoose's wrapper accessors remain functional, and + * Mongoose does not mutate the source object when constructing new + * documents during save/insertMany. + * + * @param {Object} doc - A document of the shape `{ stix, workspace }`, or + * a Mongoose document with the same shape. No-op if `doc` or `doc.stix` + * is missing. + */ +function deepFreezeStix(doc) { + const stix = doc?.stix; + if (!stix || typeof stix !== 'object' || Object.isFrozen(stix)) return; + + Object.freeze(stix); + + for (const value of Object.values(stix)) { + if (!value || typeof value !== 'object' || Object.isFrozen(value)) continue; + Object.freeze(value); + if (Array.isArray(value)) { + for (const item of value) { + if (item && typeof item === 'object' && !Object.isFrozen(item)) { + Object.freeze(item); + } + } + } + } +} + +module.exports = { + deepFreezeStix, +}; diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index c2128bc3..3221dfc0 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -24,6 +24,7 @@ const { SelfRevocationError, } = require('../../exceptions'); const { getSchema } = require('../../lib/validation-schemas'); +const { deepFreezeStix } = require('../../lib/import-safety'); const ServiceWithHooks = require('./hooks.service'); const WorkflowResult = require('../../lib/workflow-result'); @@ -700,8 +701,23 @@ class BaseService extends ServiceWithHooks { return { ...composed, warnings }; } + // Import-fidelity contract: bundle stix must be persisted byte-faithful. + // Several `beforeCreate` hooks normally rewrite stix fields (e.g. + // AnalyticsService stamps stix.name from the ATT&CK ID; SoftwareService + // defaults stix.is_family). Those rewrites are intentional on user-driven + // POST/PUT flows but must NOT run during import. We freeze stix before + // invoking the hook so any forgotten `if (!options.import)` gate throws + // a TypeError at the violating line instead of silently corrupting the + // imported content. See app/lib/import-safety.js for the full rationale. + deepFreezeStix(composed); await this.beforeCreate(composed, options); + const createdDocument = await this.repository.save(composed); + + // Same contract applies to `afterCreate` and the listeners it triggers + // via emitted domain events — anything that reaches stix on this freshly + // saved document during import must crash, not silently mutate. + deepFreezeStix(createdDocument); await this.afterCreate(createdDocument, options); await this.emitCreatedEvent(createdDocument, options); diff --git a/app/services/stix/analytics-service.js b/app/services/stix/analytics-service.js index e87b9366..1f10fd6c 100644 --- a/app/services/stix/analytics-service.js +++ b/app/services/stix/analytics-service.js @@ -11,6 +11,7 @@ const { const EventBus = require('../../lib/event-bus'); const logger = require('../../lib/logger'); const Exceptions = require('../../exceptions'); +const { deepFreezeStix } = require('../../lib/import-safety'); /** * Service for managing analytics @@ -50,15 +51,21 @@ class AnalyticsService extends BaseService { /** * Handle analytics being referenced by a detection strategy - * Add inbound embedded_relationship and update external_references + * Add inbound embedded_relationship and (when not importing) refresh the + * ATT&CK external_references URL on each referenced analytic. * * @param {Object} payload - Event payload * @param {Object} payload.detectionStrategy - The detection strategy document that references the analytics * @param {string[]} payload.analyticIds - Array of analytic STIX IDs being referenced + * @param {Object} [payload.options] - The originating create-options forwarded from + * DetectionStrategiesService.afterCreate. Used here to honor the + * import-fidelity contract — see app/lib/import-safety.js. When + * `options.import` is true, the workspace metadata update still runs but + * the stix.external_references rewrite is skipped. * @returns {Promise} */ static async handleAnalyticsReferenced(payload) { - const { detectionStrategy, analyticIds } = payload; + const { detectionStrategy, analyticIds, options } = payload; logger.info( `Analytics Service heard event: 'x-mitre-detection-strategy::analytics-referenced' for ${detectionStrategy.stix.id}`, @@ -75,6 +82,13 @@ class AnalyticsService extends BaseService { continue; } + // Import-fidelity guard: when the triggering create came from a + // bundle import, freeze this analytic's stix so any forgotten + // import gate below crashes loudly with a TypeError instead of + // silently rewriting the persisted analytic's stix fields. + // Workspace mutations are unaffected. See app/lib/import-safety.js. + if (options?.import) deepFreezeStix(analytic); + // Initialize embedded_relationships if needed if (!analytic.workspace) { analytic.workspace = {}; @@ -102,23 +116,30 @@ class AnalyticsService extends BaseService { ); } - // Update external_references with URL to parent detection strategy - if (!analytic.stix.external_references) { - analytic.stix.external_references = []; - } - - // Remove existing ATT&CK external references - analytic.stix.external_references = removeAttackExternalReferences( - analytic.stix.external_references, - ); + // Refresh the analytic's ATT&CK external_references URL so it + // points at the parent detection strategy. This rewrites + // `stix.external_references` and must therefore be skipped on the + // import path; the bundle is the source of truth for stix content. + // The framework freeze above would throw here if this gate were + // missing — that's intentional. + if (!options?.import) { + if (!analytic.stix.external_references) { + analytic.stix.external_references = []; + } - // Create new ATT&CK external reference with URL - const attackRef = createAttackExternalReference(analytic.toObject()); - if (attackRef) { - analytic.stix.external_references.unshift(attackRef); - logger.info( - `AnalyticsService: Updated external_references URL for analytic ${analyticId}`, + // Remove existing ATT&CK external references + analytic.stix.external_references = removeAttackExternalReferences( + analytic.stix.external_references, ); + + // Create new ATT&CK external reference with URL + const attackRef = createAttackExternalReference(analytic.toObject()); + if (attackRef) { + analytic.stix.external_references.unshift(attackRef); + logger.info( + `AnalyticsService: Updated external_references URL for analytic ${analyticId}`, + ); + } } await analyticsRepository.saveDocument(analytic); @@ -215,12 +236,21 @@ class AnalyticsService extends BaseService { * @throws {Exceptions.NotFoundError} If a referenced data component does not exist * @returns {Promise} */ - // eslint-disable-next-line no-unused-vars async beforeCreate(data, options) { - // Analytic name matches its ATT&CK ID - const id = data.workspace.attack_id; - data.stix.name = id.replace(/^AN(\d+)$/, 'Analytic $1'); - logger.debug(`Setting name to match ATT&CK ID: ${data.stix.name}`); + // Import-fidelity contract: when a STIX bundle is being imported, the + // bundle's `stix` content must be persisted byte-faithful. Stamping + // `stix.name` from the ATT&CK ID is correct for user-driven POST flows, + // where the server is the authority on the analytic's display name, but + // incorrect for an import — the bundle already carries the name. The + // framework freezes `data.stix` during import-mode hooks (see + // app/lib/import-safety.js), so the assignment below would throw a + // TypeError without this gate. Workspace mutations further down still + // run unconditionally. + if (!options?.import) { + const id = data.workspace.attack_id; + data.stix.name = id.replace(/^AN(\d+)$/, 'Analytic $1'); + logger.debug(`Setting name to match ATT&CK ID: ${data.stix.name}`); + } // Initialize embedded_relationships if not present if (!data.workspace) { @@ -286,11 +316,13 @@ class AnalyticsService extends BaseService { * Emits domain event to notify DataComponentsService that data components were referenced * * @param {Object} createdDocument - The created analytic document - * @param {Object} _options - Creation options (unused) + * @param {Object} [options] - Creation options forwarded from BaseService. + * Threaded into the event payload so listeners can honor the + * import-fidelity contract (no stix mutations when `options.import`). + * See app/lib/import-safety.js. * @returns {Promise} */ - // eslint-disable-next-line no-unused-vars - async afterCreate(createdDocument, _options) { + async afterCreate(createdDocument, options) { // Extract data component IDs from x_mitre_log_source_references const dataComponentRefs = createdDocument.stix?.x_mitre_log_source_references?.map( @@ -307,6 +339,7 @@ class AnalyticsService extends BaseService { analyticId: createdDocument.stix.id, analytic: createdDocument.toObject ? createdDocument.toObject() : createdDocument, dataComponentIds: dataComponentRefs, + options, }); } } diff --git a/app/services/stix/campaigns-service.js b/app/services/stix/campaigns-service.js index 3817b12f..97d0915d 100644 --- a/app/services/stix/campaigns-service.js +++ b/app/services/stix/campaigns-service.js @@ -34,10 +34,17 @@ class CampaignService extends BaseService { * Ensures aliases[0] matches the object name * * @param {Object} data - The campaign object data - * @param {Object} _options - Creation options (unused) + * @param {Object} [options] - Creation options */ - // eslint-disable-next-line no-unused-vars - async beforeCreate(data, _options) { + async beforeCreate(data, options) { + // Import-fidelity contract: skip the alias normalization on the import + // path. The normalization rewrites `stix.aliases` (rearranging entries + // and prepending `stix.name`), which is correct for user-driven flows + // but deviates the persisted analytic from the bundle source-of-truth. + // `data.stix` is frozen during import-mode hooks (app/lib/import-safety.js), + // so a missing gate here would throw a TypeError at the assignment in + // `_normalizeAliases`. + if (options?.import) return; this._normalizeAliases(data); } diff --git a/app/services/stix/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js index af6b06f1..d28e8af3 100644 --- a/app/services/stix/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -15,6 +15,7 @@ const { const logger = require('../../../lib/logger'); const config = require('../../../config/config'); const types = require('../../../lib/types'); +const { deepFreezeStix } = require('../../../lib/import-safety'); // Bounded concurrency for the compose-and-validate phase. Each task runs Zod // validation and a small amount of synchronous work, so we cap concurrency @@ -356,6 +357,12 @@ async function processTier(type, objects, ctx) { // and any other pre-persist data shaping are present on the doc when // saveMany writes it. Failures here are recorded as save errors and the // doc is dropped from the bulk insert. + // + // Import-fidelity guard: freeze stix before invoking the hook so any + // forgotten `if (!options.import)` gate inside the service crashes + // loudly with a TypeError instead of silently mutating bundle content. + // See app/lib/import-safety.js for the full contract. + deepFreezeStix(composed); try { await service.beforeCreate(composed, composeOptions); } catch (hookErr) { @@ -418,6 +425,13 @@ async function processTier(type, objects, ctx) { // Run in parallel with bounded concurrency; per-doc hook failures are // logged but never abort the import. await runWithConcurrency(inserted, COMPOSE_CONCURRENCY, async (doc) => { + // Import-fidelity guard for the post-insert lifecycle. afterCreate and + // the listeners that subscribe to the emitted `{type}::created` event + // are allowed to populate workspace metadata on referenced documents + // but must not deviate this freshly saved document's stix from the + // bundle. Freezing forces violations to crash here rather than + // silently corrupting the imported content. See app/lib/import-safety.js. + deepFreezeStix(doc); try { await service.afterCreate(doc, composeOptions); } catch (err) { diff --git a/app/services/stix/data-components-service.js b/app/services/stix/data-components-service.js index d909399f..7d2a454a 100644 --- a/app/services/stix/data-components-service.js +++ b/app/services/stix/data-components-service.js @@ -239,11 +239,13 @@ class DataComponentsService extends BaseService { * This handles both first-time creation and new version creation (versioning) * * @param {Object} createdDocument - The created data component document - * @param {Object} _options - Creation options (unused) + * @param {Object} [options] - Creation options forwarded from BaseService. + * Threaded into the event payload so listeners can honor the + * import-fidelity contract (no stix mutations when `options.import`). + * See app/lib/import-safety.js. * @returns {Promise} */ - // eslint-disable-next-line no-unused-vars - async afterCreate(createdDocument, _options) { + async afterCreate(createdDocument, options) { const addedRef = this._addedDataSourceRef; const removedRef = this._removedDataSourceRef; @@ -258,6 +260,7 @@ class DataComponentsService extends BaseService { dataComponentId: createdDocument.stix.id, dataComponent: createdDocument.toObject ? createdDocument.toObject() : createdDocument, dataSourceId: addedRef, + options, }); } @@ -271,6 +274,7 @@ class DataComponentsService extends BaseService { await EventBus.emit('x-mitre-data-component::data-source-removed', { dataComponentId: createdDocument.stix.id, dataSourceId: removedRef, + options, }); } @@ -287,6 +291,7 @@ class DataComponentsService extends BaseService { dataComponentId: createdDocument.stix.id, dataComponent: createdDocument.toObject ? createdDocument.toObject() : createdDocument, dataSourceId: currentDataSourceRef, + options, }); } diff --git a/app/services/stix/detection-strategies-service.js b/app/services/stix/detection-strategies-service.js index b3715eeb..d8ddb92f 100644 --- a/app/services/stix/detection-strategies-service.js +++ b/app/services/stix/detection-strategies-service.js @@ -114,8 +114,14 @@ class DetectionStrategiesService extends BaseService { * Handle post-creation logic * Emit domain events to notify AnalyticsService about referenced/removed analytics * This handles both first-time creation and new version creation (versioning) + * + * @param {Object} document - The persisted detection strategy + * @param {Object} [options] - Create options forwarded from BaseService. + * Threaded into the event payload so listeners can honor the + * import-fidelity contract (no stix mutations when `options.import`). + * See app/lib/import-safety.js for the contract. */ - async afterCreate(document) { + async afterCreate(document, options) { const addedRefs = this._addedAnalyticRefs || []; const removedRefs = this._removedAnalyticRefs || []; @@ -130,6 +136,7 @@ class DetectionStrategiesService extends BaseService { detectionStrategyId: document.stix.id, detectionStrategy: document.toObject ? document.toObject() : document, analyticIds: addedRefs, + options, }); } @@ -143,6 +150,7 @@ class DetectionStrategiesService extends BaseService { await EventBus.emit('x-mitre-detection-strategy::analytics-removed', { detectionStrategyId: document.stix.id, analyticIds: removedRefs, + options, }); } @@ -159,6 +167,7 @@ class DetectionStrategiesService extends BaseService { detectionStrategyId: document.stix.id, detectionStrategy: document.toObject ? document.toObject() : document, analyticIds: currentAnalyticRefs, + options, }); } diff --git a/app/services/stix/groups-service.js b/app/services/stix/groups-service.js index b182470d..552444d8 100644 --- a/app/services/stix/groups-service.js +++ b/app/services/stix/groups-service.js @@ -34,10 +34,16 @@ class GroupsService extends BaseService { * Ensures aliases[0] matches the object name * * @param {Object} data - The group object data - * @param {Object} _options - Creation options (unused) + * @param {Object} [options] - Creation options */ - // eslint-disable-next-line no-unused-vars - async beforeCreate(data, _options) { + async beforeCreate(data, options) { + // Import-fidelity contract: skip the alias normalization on the import + // path. The normalization rewrites `stix.aliases`, which is correct for + // user-driven flows but deviates the persisted group from the bundle + // source-of-truth. `data.stix` is frozen during import-mode hooks + // (app/lib/import-safety.js), so a missing gate here would throw a + // TypeError at the assignment in `_normalizeAliases`. + if (options?.import) return; this._normalizeAliases(data); } diff --git a/app/services/stix/software-service.js b/app/services/stix/software-service.js index 896d0d9b..fb7651f8 100644 --- a/app/services/stix/software-service.js +++ b/app/services/stix/software-service.js @@ -40,10 +40,19 @@ class SoftwareService extends BaseService { * - Ensures x_mitre_aliases[0] matches the object name * * @param {Object} data - The software object data - * @param {Object} _options - Creation options (unused) + * @param {Object} [options] - Creation options */ - // eslint-disable-next-line no-unused-vars - async beforeCreate(data, _options) { + async beforeCreate(data, options) { + // Import-fidelity contract: defaulting `stix.is_family` and rewriting + // `stix.x_mitre_aliases` is correct for user-driven flows where the + // server is the authority on these fields, but incorrect on the import + // path — the bundle carries authoritative values (including a deliberate + // omission of `is_family` for malware that doesn't represent a family, + // which must NOT be defaulted to `true`). `data.stix` is frozen during + // import-mode hooks (app/lib/import-safety.js), so a missing gate would + // throw a TypeError at the first attempted stix write below. + if (options?.import) return; + // Set is_family default for malware if (data.stix && data.stix.type === MalwareType && typeof data.stix.is_family !== 'boolean') { data.stix.is_family = true; From d69472ea3af2201dc0e354d696687e1a2943ebb1 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 11:12:48 -0400 Subject: [PATCH 337/370] fix(import-bundle): surface ADM validation errors in import response The bundle-import pipeline's fail-open branch (the default, when `?validateContents=true` is not set) attached the ADM error list to each failing document's `workspace.validation` but never wrote a matching entry to the import response's `workspace.import_categories.errors`. A bundle with hundreds of validation failures could look like a clean import. composeForImport now also returns the full validationErrors array; processTier writes one entry per failing object with `error_type: validationError`, a summary `error_message`, and a `details` array containing every `{message, path, code}` from the Zod output. This applies in both branches: - validateContents=true (strict): the doc is dropped from the bulk insert and the error is recorded with full details. - validateContents=false (default): the doc is still persisted with workspace.validation attached, but the error is also mirrored into import_categories.errors so the response surfaces the failure up front. Also fixes the strict-branch entry's error_type, which was previously `saveError` despite the failure being a validation failure. --- app/services/meta-classes/base.service.js | 16 +++++- .../import-bundle.js | 54 ++++++++++++++++--- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/app/services/meta-classes/base.service.js b/app/services/meta-classes/base.service.js index 3221dfc0..ee174b25 100644 --- a/app/services/meta-classes/base.service.js +++ b/app/services/meta-classes/base.service.js @@ -741,7 +741,19 @@ class BaseService extends ServiceWithHooks { * * @param {Object} data - The request data ({ stix, workspace }) * @param {Object} options - Options passed from create() - * @returns {Promise<{ data: Object, warnings: Array, throwIfValidating: ValidationError|null }>} + * @returns {Promise<{ + * data: Object, + * warnings: Array, + * throwIfValidating: ValidationError|null, + * validationErrors: Array<{message,path,code,input}> + * }>} + * - `validationErrors` is the full list of ADM errors that fired against + * this object (after bypass filtering). The bundle-import path uses it + * to surface per-object validation failures in + * `workspace.import_categories.errors` so an import response + * accurately reflects what was wrong — without that, fail-open mode + * silently buries the errors on each document and the import looks + * successful even when many objects are malformed. */ async composeForImport(data, options) { // Strip workspace.validation — server-controlled; the fail-open block @@ -796,7 +808,7 @@ class BaseService extends ServiceWithHooks { } } - return { data, warnings, throwIfValidating }; + return { data, warnings, throwIfValidating, validationErrors: errors }; } /** diff --git a/app/services/stix/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js index d28e8af3..74f58a94 100644 --- a/app/services/stix/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -334,17 +334,27 @@ async function processTier(type, objects, ctx) { }; try { - const { data: composed, throwIfValidating } = await service.composeForImport( - stagingDoc, - composeOptions, - ); - + const { + data: composed, + throwIfValidating, + validationErrors, + } = await service.composeForImport(stagingDoc, composeOptions); + + // Strict-mode validation failure (`validateContents=true`). Surface the + // full ADM error list, not just the wrapper message — without `details` + // the caller has no way to act on the failure other than re-running the + // import with logs at debug level. Drop the doc from the bulk insert. if (throwIfValidating) { const importError = { object_ref: importObject.id, object_modified: importObject.modified, - error_type: importErrors.saveError, - error_message: throwIfValidating.message, + error_type: importErrors.validationError, + error_message: `${validationErrors.length} ADM validation error(s)`, + details: validationErrors.map((e) => ({ + message: e.message, + path: e.path, + code: e.code, + })), }; logger.verbose( `Import Bundle Error: Validation failed. id=${importObject.id}, ${throwIfValidating.message}`, @@ -353,6 +363,36 @@ async function processTier(type, objects, ctx) { return; } + // Fail-open validation failures (`validateContents=false`, the default). + // The object IS persisted with the error list attached to its own + // `workspace.validation`, but a clean import response would otherwise + // give the caller no signal that anything was wrong. We mirror the + // per-object errors into `import_categories.errors` so the response + // surfaces them up front. One taxonomy entry per object regardless of + // how many issues that object had — the full per-issue list lives in + // `details` so the caller can drill down without querying each doc. + if (validationErrors.length > 0) { + const firstFew = validationErrors + .slice(0, 3) + .map((e) => e.message) + .join('; '); + const summary = + validationErrors.length > 3 + ? `${firstFew}; ...and ${validationErrors.length - 3} more` + : firstFew; + importedCollection.workspace.import_categories.errors.push({ + object_ref: importObject.id, + object_modified: importObject.modified, + error_type: importErrors.validationError, + error_message: `${validationErrors.length} ADM validation error(s): ${summary}`, + details: validationErrors.map((e) => ({ + message: e.message, + path: e.path, + code: e.code, + })), + }); + } + // Run the service's beforeCreate hook so outbound embedded_relationships // and any other pre-persist data shaping are present on the doc when // saveMany writes it. Failures here are recorded as save errors and the From 029b0e1ebf63fe6918b5f0c113ce2dcc27928266 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 11:13:57 -0400 Subject: [PATCH 338/370] docs(import-bundle): add user guide, pipeline overview, fidelity contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three new documents covering the bundle-import work: - docs/user/stix-bundle-import.md — how to call the endpoint, the two validation modes (fail-open vs strict), the import_categories.errors taxonomy, and re-import semantics. - docs/developer/stix-bundle-import-pipeline.md — implementer walk-through of the tier-batched pipeline with stage diagrams, the dependency-tier table, concurrency primitives, and bulk persistence helpers. - docs/developer/import-fidelity-contract.md — the stix-frozen contract for hooks and listeners, what the framework enforces, and the author rules for adding new lifecycle code. --- docs/developer/import-fidelity-contract.md | 203 +++++++++++++++ docs/developer/stix-bundle-import-pipeline.md | 234 ++++++++++++++++++ docs/user/stix-bundle-import.md | 171 +++++++++++++ 3 files changed, 608 insertions(+) create mode 100644 docs/developer/import-fidelity-contract.md create mode 100644 docs/developer/stix-bundle-import-pipeline.md create mode 100644 docs/user/stix-bundle-import.md diff --git a/docs/developer/import-fidelity-contract.md b/docs/developer/import-fidelity-contract.md new file mode 100644 index 00000000..d087ba65 --- /dev/null +++ b/docs/developer/import-fidelity-contract.md @@ -0,0 +1,203 @@ +# Import-Fidelity Contract + +When a STIX bundle is imported, the persisted objects must be +**byte-faithful** to the bundle's `stix` content. Workbench may +populate its private `workspace` metadata on each document, but +must not deviate any field under `stix`. + +This document defines the contract, explains why it exists, and +tells you how to author hooks, event listeners, and any new code +that runs during a bundle import. + +For the broader bundle-import pipeline, see +[`stix-bundle-import-pipeline.md`](./stix-bundle-import-pipeline.md). + +## Why the contract exists + +Workbench has a richer object model than raw STIX. Lifecycle hooks +and event listeners legitimately normalize and enrich STIX fields +on user-driven flows. A few examples that ship today: + +| Service | Hook / listener | Stix mutation | +|---|---|---| +| AnalyticsService | `beforeCreate` | Stamps `stix.name = "Analytic "` | +| AnalyticsService | `handleAnalyticsReferenced` listener | Rewrites `stix.external_references` to embed a URL to the parent detection strategy | +| CampaignsService | `beforeCreate` | Forces `stix.aliases[0]` to equal `stix.name` | +| GroupsService | `beforeCreate` | Same alias normalization as campaigns | +| SoftwareService | `beforeCreate` | Defaults `stix.is_family = true` for malware; normalizes `stix.x_mitre_aliases` | + +All five are correct on POST/PUT, where Workbench is the authority +on the object's display values. None of them are correct during an +import: the bundle is the source of truth for `stix.*`, and a +silent rewrite breaks round-trip fidelity and obscures the +provenance of the imported content. + +The framework therefore enforces a hard rule: + +> **During an import (`options.import === true`), `stix.*` is +> read-only. Workspace fields are still mutable.** + +## How the contract is enforced + +[`app/lib/import-safety.js`](../../app/lib/import-safety.js) exports +`deepFreezeStix(doc)`. It calls `Object.freeze` on `doc.stix` and on +the immediate children (nested objects, nested arrays, and the +array elements). In Node strict mode (`'use strict'` at the top of +every service file), an attempted write to any frozen property +throws `TypeError` immediately, pointing at the violating line. + +The framework calls `deepFreezeStix` at every point where untrusted +code (a hook, a listener) is about to run during an import: + +| Location | When | +|---|---| +| `BaseService._createFromImport` | Before `beforeCreate(composed, options)` and before `afterCreate(doc, options)` / `emitCreatedEvent(doc, options)`. | +| `collection-bundles-service/import-bundle.js` (compose worker) | Before each call to `service.beforeCreate(composed, composeOptions)`. | +| `collection-bundles-service/import-bundle.js` (post-insert worker) | Before each call to `service.afterCreate(doc, composeOptions)` and `service.emitCreatedEvent(doc, composeOptions)`. | + +Listeners that fetch a related document and mutate it then call +`deepFreezeStix(fetchedDoc)` themselves on entry when their +incoming payload indicates an import is in progress. The pattern +is one line: + +```js +if (payload.options?.import) deepFreezeStix(fetchedDoc); +``` + +The freeze is invisible to non-import paths: `deepFreezeStix` is +only called when the framework or a listener has confirmed +`options.import === true`. + +### Why deep, not shallow + +`Object.freeze` is shallow. Common stix mutations target nested +structures: + +- `analytic.stix.external_references.unshift(...)` (rewrites an array) +- `analytic.stix.external_references[0].external_id = '...'` (mutates an array element) + +A shallow freeze would let both succeed silently. `deepFreezeStix` +walks one level into objects and arrays (including array elements) +to cover the cases STIX content actually exhibits. Reads remain +unaffected at any depth. + +### Why freeze instead of clone-and-restore + +A snapshot-and-restore approach (clone `stix` before each hook, +restore after) would also enforce fidelity, but it requires the +framework to know which side effects to undo and risks leaving +half-written state when a hook mutates nested objects. A freeze +fails closed at the violating line, points the developer at +exactly the code that needs a gate, and adds zero runtime +overhead after the freeze is applied. + +## Author rules — writing hooks and listeners + +The contract translates into one rule for hook authors: + +> Workspace mutations are always allowed. Wrap any `stix.*` +> mutation in `if (!options?.import) { ... }`. + +A correctly-shaped `beforeCreate` looks like this: + +```js +async beforeCreate(data, options) { + // Workspace mutations — always allowed. + data.workspace = data.workspace || {}; + data.workspace.embedded_relationships = buildOutboundRels(data); + + // STIX mutations — gated. The framework freezes data.stix + // during import, so a missing gate throws a TypeError pointing + // at the line below on the first import test. + if (!options?.import) { + data.stix.name = deriveNameFromAttackId(data.workspace.attack_id); + } +} +``` + +And a correctly-shaped listener: + +```js +static async handleAnalyticsReferenced(payload) { + const { detectionStrategy, analyticIds, options } = payload; + + for (const analyticId of analyticIds) { + const analytic = await analyticsRepository.retrieveLatestByStixId(analyticId); + if (!analytic) continue; + + // Import-fidelity guard. The framework freezes the doc the + // emitter saw, but listeners fetch their own related docs — + // so each listener takes responsibility for freezing what + // it fetched. + if (options?.import) deepFreezeStix(analytic); + + // Workspace mutations — always allowed. + addInboundEmbeddedRelationship(analytic, detectionStrategy); + + // STIX mutations — gated, just like in beforeCreate. + if (!options?.import) { + refreshExternalReferencesUrl(analytic, detectionStrategy); + } + + await analyticsRepository.saveDocument(analytic); + } +} +``` + +## Forwarding `options` to listeners + +Listeners can only honor the contract if the originating service +forwards its create-options into the emitted event payload. The +three afterCreate emit sites that drive metadata cascades all do +this: + +- `DetectionStrategiesService.afterCreate(document, options)` + passes `options` into every `'x-mitre-detection-strategy::*'` + emit. +- `AnalyticsService.afterCreate(createdDocument, options)` + passes `options` into `'x-mitre-analytic::data-components-referenced'`. +- `DataComponentsService.afterCreate(createdDocument, options)` + passes `options` into `'x-mitre-data-component::data-source-*'`. + +If you add a new domain event that may fire during import, do the +same — include `options` in the payload. + +## Adding a new hook or listener + +Checklist for an author adding code that runs during a bundle +import: + +1. **Default to workspace.** If your work can be expressed as + workspace metadata (an embedded relationship, a derived index, + a denormalized cache), keep it under `workspace.*` — no gate + needed. + +2. **Gate stix writes.** If you genuinely need to mutate + `stix.*`, wrap the block in `if (!options?.import) { ... }`. + Forgetting the gate will not silently break things: the next + import test will crash with a TypeError pointing at your line. + +3. **Listeners freeze what they fetch.** If your listener fetches + a related document via a repository and may write to its + `stix.*`, add `if (options?.import) deepFreezeStix(fetched);` + at the top of the per-document block. + +4. **Emit `options` in event payloads.** If your service emits a + domain event from `afterCreate` / `afterUpdate`, include + `options` in the payload so downstream listeners can see when + an import is in progress. + +5. **Test it.** Round-trip a bundle through import — export, hash + the persisted `stix` content of a sample of objects, compare + to the bundle. If you forgot a gate, you'll have crashed on + the import attempt before you ever reach the comparison. + +## Files + +| Path | Role | +|---|---| +| [`app/lib/import-safety.js`](../../app/lib/import-safety.js) | `deepFreezeStix` helper and contract documentation. | +| [`app/services/meta-classes/base.service.js`](../../app/services/meta-classes/base.service.js) | Framework-level freeze in `_createFromImport`. | +| [`app/services/stix/collection-bundles-service/import-bundle.js`](../../app/services/stix/collection-bundles-service/import-bundle.js) | Framework-level freeze in the bulk pipeline. | +| [`app/services/stix/analytics-service.js`](../../app/services/stix/analytics-service.js) | Example of both forms of gate (`beforeCreate` and listener). | +| [`app/services/stix/detection-strategies-service.js`](../../app/services/stix/detection-strategies-service.js) | Example of forwarding `options` into event payloads. | diff --git a/docs/developer/stix-bundle-import-pipeline.md b/docs/developer/stix-bundle-import-pipeline.md new file mode 100644 index 00000000..5dbf77fe --- /dev/null +++ b/docs/developer/stix-bundle-import-pipeline.md @@ -0,0 +1,234 @@ +# STIX Bundle Import Pipeline + +This document describes the internal pipeline that runs when a +client POSTs to `/api/collection-bundles`. For user-facing +documentation of the endpoint's behavior and response shape, see +[`docs/user/stix-bundle-import.md`](../user/stix-bundle-import.md). + +For the rules that govern hook and listener behavior during import +(why bundle `stix` content stays byte-faithful through the +pipeline), see [`import-fidelity-contract.md`](./import-fidelity-contract.md). + +## Entry points + +``` +HTTP request + → app/controllers/collection-bundles-controller.js:importBundle + → app/services/stix/collection-bundles-service/index.js + → app/services/stix/collection-bundles-service/import-bundle.js (this pipeline) +``` + +The controller reads query parameters (`previewOnly`, +`validateContents`, `forceImport`) into an `options` object and +hands the entire bundle and options to `importBundle`. + +## Pipeline stages + +``` +┌────────────────────────────────────────────────────────────────┐ +│ 1. Initialize per-import state │ +│ - importedCollection skeleton (workspace.import_categories) │ +│ - contentsMap from collection.x_mitre_contents │ +│ - referenceMap │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ 2. Check for duplicate collection │ +│ - Same stixId + same modified → existing collection │ +│ - forceImport=duplicate-collection: warn, attach a reimport │ +│ - Otherwise: throw, abort │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ 3. processObjects │ +│ - Sort objects by dependency order │ +│ - Group consecutive same-type objects into TIERS │ +│ - For each tier sequentially: processTier(type, objects) │ +│ - Then: report contents-map orphans as "Missing object" │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ 4. importReferences (sequential) │ +│ - Insert or update each unique external_reference │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ 5. saveCollection │ +│ - Persist x-mitre-collection itself (or append reimport) │ +└────────────────────────────────────────────────────────────────┘ +``` + +## Dependency-ordered tiers + +`sortObjectsByDependencies` returns the bundle's objects in this +order (lower numbers persist first): + +| Tier | STIX type | Rationale | +|---|---|---| +| 0 | `marking-definition` | No outbound refs to other types | +| 1 | `identity` | No outbound refs to other types | +| 2 | `x-mitre-data-source` | Data components reference these | +| 3 | `x-mitre-data-component` | Analytics reference these | +| 4 | `x-mitre-analytic` | Detection strategies reference these | +| 5 | `x-mitre-detection-strategy` | (depends on analytics) | +| 6 | `attack-pattern` (techniques) | SDOs in general | +| 7 | `x-mitre-tactic` | | +| 8 | `course-of-action` (mitigations) | | +| 9 | `intrusion-set` (groups) | | +| 10 | `campaign` | | +| 11 | `malware` | | +| 12 | `tool` | | +| 13 | `x-mitre-asset` | | +| 14 | `x-mitre-matrix` | | +| 15 | `relationship` | SROs last so their endpoints exist | +| 16 | `note` | | +| 17 | `x-mitre-collection` | The bundle's own collection (skipped here; persisted separately) | + +Sort is stable, so within a tier order matches the bundle's order. + +## `processTier` — what runs inside one tier + +For each tier (objects of a single STIX type, in dependency order): + +``` +┌────────────────────────────────────────────────────────────────┐ +│ A. Synchronous eligibility filter (single pass over tier) │ +│ - contents-map drain (warn on bundle-object-not-in-contents)│ +│ - ATT&CK spec-version gate (throws or skips per forceImport)│ +│ → eligible[] │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ B. Bulk pre-fetch existing versions │ +│ repository.retrieveAllByStixIds(eligible.map(o => o.id)) │ +│ → Map> │ +│ One DB query for the entire tier, replacing N retrieveById │ +│ calls from the legacy per-object loop. │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ C. Parallel compose & validate (bounded concurrency, cap 25) │ +│ For each eligible object: │ +│ - checkForDuplicate vs pre-fetched versions │ +│ - categorizeObject (additions/changes/etc.) │ +│ - processExternalReferences │ +│ - service.composeForImport (Zod + workspace.attack_id + │ +│ fail-open workspace.validation) │ +│ - deepFreezeStix(composed) — import-fidelity guard │ +│ - service.beforeCreate(composed, options) │ +│ - record any validation errors into │ +│ importedCollection.workspace.import_categories.errors │ +│ - push composed doc into composedToInsert[] │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ D. Bulk insert (one MongoDB insertMany per tier) │ +│ repository.saveMany(composedToInsert) │ +│ → { inserted: [...], errors: [...writeErrors] } │ +│ writeErrors (ordered:false) → import_categories.errors │ +└────────────────────────────────────────────────────────────────┘ + │ +┌────────────────────────────────────────────────────────────────┐ +│ E. Parallel post-insert lifecycle (bounded concurrency, cap 25)│ +│ For each inserted doc: │ +│ - deepFreezeStix(doc) — import-fidelity guard │ +│ - service.afterCreate(doc, options) │ +│ - service.emitCreatedEvent(doc, options) │ +│ afterCreate emits domain events (e.g. │ +│ 'x-mitre-detection-strategy::analytics-referenced') │ +│ that drive INBOUND workspace.embedded_relationships │ +│ population on referenced docs in earlier-finished tiers. │ +└────────────────────────────────────────────────────────────────┘ +``` + +The order of tiers matters because the post-insert listener +cascade in stage E may modify documents from earlier tiers +(e.g. analytics that were persisted in tier 4 receive inbound +embedded_relationships when detection strategies in tier 5 are +processed). + +## Concurrency primitives + +`runWithConcurrency(items, limit, task)` in `import-bundle.js` is a +small worker-pool helper used in stages C and E. It pulls from a +shared index so each worker fetches the next available item rather +than partitioning ahead of time, which keeps utilization high even +when per-item cost varies (a worker that finishes a cheap doc +immediately picks up the next one). + +We do not pull in `p-limit`: it is not a direct dependency of this +project, and recent versions are ESM-only, which doesn't fit a +CommonJS codebase. The helper is small enough to keep inline. + +## Bulk persistence primitives + +Both new repository methods live on `_base.repository.js` and are +inherited by every concrete repository: + +- **`retrieveAllByStixIds(stixIds)`** — single `find({ 'stix.id': + { $in: ids } })` followed by an in-memory bucket by stixId. + Returns `Map>` with versions sorted + newest-first (matching `retrieveAllById`'s order). + +- **`saveMany(dataArr, { ordered })`** — wraps + `Model.insertMany(dataArr, { ordered: false })`. Returns + `{ inserted, errors }` where `errors` is a normalized + `{ index, message, code }` per failed document. `ordered: false` + ensures one bad document does not abort the rest of the tier. + +Both are only invoked from the bundle-import path. The +single-object create/update paths continue to use +`repository.save(data)` and `repository.retrieveLatestByStixId`. + +## Error model + +The pipeline never throws for per-object failures (except the +ATT&CK spec-version violation without forceImport, which is +considered fatal). Every recoverable error is appended to +`importedCollection.workspace.import_categories.errors` with a +typed `error_type` and as much context as is available. See the +[user doc](../user/stix-bundle-import.md#error-types-in-import_categorieserrors) +for the full error-type taxonomy. + +ADM validation errors are recorded in **both** branches: + +- `validateContents=true` (strict): the doc is dropped from the + bulk insert; one entry with full `details` is written. +- `validateContents=false` (fail-open, default): the doc is still + persisted with `workspace.validation` attached; an entry with + full `details` is **also** written so the import response + surfaces the failure up front. + +Both branches use `error_type: 'Validation error'` and include +the complete Zod issue list in the entry's `details` field. + +## Performance characteristics + +The pipeline scales primarily with two factors: + +1. **Number of objects in the bundle.** The MongoDB round-trips + are dominated by per-tier reads and writes, both of which are + O(1) queries per tier regardless of the number of objects in + it. The total round-trip count is ~`2 * number_of_tiers`. + +2. **Cost of the listener cascade.** Each `afterCreate` that emits + a domain event triggers a listener that fetches and updates the + referenced documents. This is currently O(refs) per source + object — if 10 analytics reference one data component, that + data component is fetched and saved 10 times. Consolidating + listener writes per target is a future optimization. + +On developer hardware, an Enterprise bundle import that previously +took 5+ minutes completes in 20-60 seconds depending on local +Mongo and CPU. + +## Files + +| Path | Role | +|---|---| +| [`app/services/stix/collection-bundles-service/import-bundle.js`](../../app/services/stix/collection-bundles-service/import-bundle.js) | The pipeline itself. `processObjects`, `processTier`, `runWithConcurrency`. | +| [`app/services/stix/collection-bundles-service/bundle-helpers.js`](../../app/services/stix/collection-bundles-service/bundle-helpers.js) | Constants for `importErrors`, `forceImportParameters`, `errors`. | +| [`app/services/meta-classes/base.service.js`](../../app/services/meta-classes/base.service.js) | `composeForImport` (validation + workspace fields) and `_createFromImport` (single-object path). | +| [`app/repository/_base.repository.js`](../../app/repository/_base.repository.js) | `retrieveAllByStixIds` and `saveMany`. | +| [`app/lib/validation-schemas.js`](../../app/lib/validation-schemas.js) | ADM schema selection with cached `.partial()` for WIP objects. | +| [`app/lib/import-safety.js`](../../app/lib/import-safety.js) | `deepFreezeStix` enforcement helper. | diff --git a/docs/user/stix-bundle-import.md b/docs/user/stix-bundle-import.md new file mode 100644 index 00000000..1d6e6f1c --- /dev/null +++ b/docs/user/stix-bundle-import.md @@ -0,0 +1,171 @@ +# Importing a STIX Bundle + +The ATT&CK Workbench REST API can ingest a STIX 2.1 bundle that wraps an +ATT&CK collection (`x-mitre-collection`). The endpoint persists every +object in the bundle, populates ATT&CK Workbench metadata on each one, +and returns a single response document summarizing what happened. + +Bundles are imported **as-is**: the `stix` content of every persisted +object matches what was in the bundle, byte-for-byte. Workbench adds +metadata in a separate `workspace` namespace but does not alter the +bundle's STIX fields. This guarantee holds even when Workbench's +hooks would otherwise rewrite fields like `stix.name`, +`stix.aliases`, or `stix.external_references` on a user-driven POST. + +## Usage + +``` +POST /api/collection-bundles +Content-Type: application/json +``` + +**Request body**: a STIX 2.1 bundle whose `objects` array contains +exactly one `x-mitre-collection` object and any number of +collection-member objects. + +### Query parameters + +| Parameter | Type | Default | Effect | +|---|---|---|---| +| `previewOnly` | boolean | `false` | Process the bundle and return the would-be import response without persisting anything. | +| `checkOnly` | boolean | `false` | Synonymous with `previewOnly` for backwards compatibility. | +| `validateContents` | boolean | `false` | When `true`, ADM validation is strict — see [Validation modes](#validation-modes). When `false` (default), validation runs but failures are recorded rather than rejected. | +| `forceImport` | repeated string | (none) | Allow import to proceed past specific blocking conditions. Supported values: `attack-spec-version-violations`, `duplicate-collection`. | + +## Validation modes + +Every object in the bundle is validated against the ATT&CK Data Model +(ADM) schemas during import — provided that ADM validation is enabled +in the deployment (`VALIDATE_WITH_ADM_SCHEMAS`, default `true`). +Objects marked `revoked: true` or `x_mitre_deprecated: true` skip +validation; everything else is checked. + +The behavior on validation failure depends on `validateContents`: + +### Default mode — `validateContents=false` (or unset) + +**Fail-open.** A failing object is still persisted, but two pieces of +state are written so the failure is visible: + +1. **On the document itself**, in `workspace.validation`: + + ```jsonc + "workspace": { + "validation": { + "errors": [ + { "message": "type is Invalid literal value", "path": ["type"], "code": "invalid_literal" } + ], + "attack_spec_version": "3.3.0", + "adm_version": "4.11.7", + "validated_at": "2026-05-14T12:00:00.000Z" + } + } + ``` + +2. **On the collection's import response**, in + `workspace.import_categories.errors`, one entry per failing + object: + + ```jsonc + { + "object_ref": "attack-pattern--1234...", + "object_modified": "2024-10-15T14:00:21.000Z", + "error_type": "Validation error", + "error_message": "3 ADM validation error(s): path.x is invalid_type; ...", + "details": [ + { "message": "x_mitre_platforms is Required", "path": ["x_mitre_platforms"], "code": "invalid_type" }, + { "message": "...", "path": ["..."], "code": "..." } + ] + } + ``` + + The `details` array preserves the full Zod issue list so callers + can act on the failure without fetching every object individually. + +Fail-open mode is the default because bundle import is the primary +way that legacy and version-skewed content enters the system; aborting +on every ADM mismatch would make migrations between ATT&CK versions +impossible. + +### Strict mode — `validateContents=true` + +A failing object is **not** persisted. The entry in +`import_categories.errors` is written exactly as above (with full +`details`), but the document is dropped from the bulk insert. Other +objects in the same bundle continue to be processed. The import as a +whole succeeds; only the failing objects are missing from the database. + +Use strict mode when you want the import to be a clean filter: only +objects that pass current ADM validation will be persisted, and the +import response tells you exactly which ones were rejected and why. + +## Reading the import response + +The response is the persisted `x-mitre-collection` document. Look at +`workspace.import_categories`: + +```jsonc +"workspace": { + "imported": "2026-05-14T12:00:00.000Z", + "import_categories": { + "additions": [ /* stixIds of new objects */ ], + "changes": [ /* stixIds where x_mitre_version increased */ ], + "minor_changes": [ /* stixIds where only modified changed */ ], + "revocations": [ /* stixIds newly revoked in this version */ ], + "deprecations": [ /* stixIds newly deprecated in this version */ ], + "supersedes_user_edits": [ ], + "supersedes_collection_changes": [ ], + "duplicates": [ /* stixIds whose modified matches an existing version */ ], + "out_of_date": [ /* stixIds where existing modified is newer */ ], + "errors": [ /* see below */ ] + }, + "import_references": { + "additions": [ /* source_names of newly inserted references */ ], + "changes": [ /* source_names of updated references */ ], + "duplicates": [ ] + } +} +``` + +### Error types in `import_categories.errors` + +| `error_type` | Meaning | +|---|---| +| `Validation error` | The object failed ADM schema validation. The `details` array contains every `{message, path, code}`. In fail-open mode the object is still persisted; in strict mode it is dropped. | +| `Save error` | A persistence failure (e.g. MongoDB duplicate-key race). | +| `Retrieval error` | The bulk pre-fetch for the tier failed; no object in that tier was processed. | +| `Not in contents` | The object exists in the bundle's `objects` array but is missing from the collection's `x_mitre_contents`. It is still persisted; this is a warning. | +| `Missing object` | The object is listed in `x_mitre_contents` but is missing from the bundle. | +| `Unknown object type` | The object's `type` is not one the server knows how to persist. | +| `ATT&CK Spec version violation` | The object's `x_mitre_attack_spec_version` is later than the server supports. Without `forceImport=attack-spec-version-violations`, the entire import aborts when this occurs. | +| `Duplicate collection object` | A second `x-mitre-collection` matching an already-persisted collection was found. Without `forceImport=duplicate-collection`, the import aborts. | + +## Re-importing the same bundle + +Re-importing a bundle whose collection (`x-mitre-collection`) already +exists at the same `modified` timestamp augments the existing +collection rather than creating a duplicate: the new import's +`import_categories` is appended to `workspace.reimports` and member +objects are upserted version-by-version. Members that match an +existing version exactly (same `modified`) appear in `duplicates`; +members whose `modified` is newer than what's stored appear in +`additions` / `changes` / `minor_changes` as appropriate. + +## Performance + +For very large bundles (the Enterprise ATT&CK bundle ships ~5,000 +objects), the import runs in tier-batched parallel passes — see +[`docs/developer/stix-bundle-import-pipeline.md`](../developer/stix-bundle-import-pipeline.md) +for the implementation detail. Typical wall-clock times on developer +hardware: + +| Bundle | Approximate import time | +|---|---| +| Mobile ATT&CK | < 5 seconds | +| ICS ATT&CK | < 5 seconds | +| Enterprise ATT&CK | 20-60 seconds (depending on hardware and Mongo configuration) | + +If the request seems hung past a minute, check the server logs for +`Import Bundle Error` entries — most often the cause is a deeper +issue (e.g. a Mongo connection problem) rather than continued +processing. From b86d506587259819d61e0e462901479b8c55009e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 11:56:23 -0400 Subject: [PATCH 339/370] fix(import-bundle): skip x_mitre_contents check for marking-definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The export side (stix-bundles-service) deliberately omits marking-definitions from `x_mitre_contents` — they are referenced on the collection via `object_marking_refs` instead. The import side did not mirror that convention, so every well-formed bundle produced one bogus "Not in contents" warning per marking-definition. Extend the existing `x-mitre-collection` exemption in processTier to also cover `marking-definition`. No change to map construction or duplicate-detection — only the false-positive warning is suppressed. --- .../collection-bundles-service/import-bundle.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/services/stix/collection-bundles-service/import-bundle.js b/app/services/stix/collection-bundles-service/import-bundle.js index 74f58a94..60924e63 100644 --- a/app/services/stix/collection-bundles-service/import-bundle.js +++ b/app/services/stix/collection-bundles-service/import-bundle.js @@ -222,9 +222,20 @@ async function processTier(type, objects, ctx) { // compose-and-insert. const eligible = []; for (const importObject of objects) { + // The contents-map check verifies that every imported object is also + // listed in the collection's `x_mitre_contents`. Two types are exempt + // because the export side (stix-bundles-service) deliberately omits them + // from `x_mitre_contents`: + // - `x-mitre-collection`: the collection is the container; it doesn't + // list itself. + // - `marking-definition`: marking-defs are referenced by the + // collection via `object_marking_refs` instead. Treating their + // absence from x_mitre_contents as a warning produces a false + // positive on every well-formed bundle (one per marking-def). if ( !contentsMap.delete(makeKeyFromObject(importObject)) && - importObject.type !== types.Collection + importObject.type !== types.Collection && + importObject.type !== types.MarkingDefinition ) { const importError = { object_ref: importObject.id, From 67851850d6a7818ed52941ebbc17918d56d613da Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 14 May 2026 12:31:11 -0400 Subject: [PATCH 340/370] fix(import-bundle): surface Mongoose validation errors on bulk insert repository.saveMany() delegates to Model.insertMany() with ordered:false. Mongoose's default behavior is to silently drop docs that fail schema validation, so malformed documents were vanishing from the bulk path without producing any entry in import_categories.errors. The old per-object save() path surfaced the same failure as a "Save error", which two regression tests relied on. Pass throwOnValidationError:true so Mongoose throws MongooseBulkWriteError after attempting the valid docs, then walk err.results in order to map each failure back to its source index and produce a {index, message, code} entry alongside the existing MongoBulkWriteError handling. The caller in processTier is unchanged. --- app/repository/_base.repository.js | 53 ++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/app/repository/_base.repository.js b/app/repository/_base.repository.js index 85f80f04..fbd7abc7 100644 --- a/app/repository/_base.repository.js +++ b/app/repository/_base.repository.js @@ -401,9 +401,16 @@ class BaseRepository extends AbstractRepository { /** * Bulk insert. Used by the STIX bundle import path to avoid one round-trip - * per object. `ordered: false` keeps MongoDB inserting the remaining docs - * after an individual failure; per-doc errors are returned on the thrown - * BulkWriteError's `writeErrors` for the caller to fold into import errors. + * per object. + * + * `ordered: false` keeps MongoDB inserting the remaining docs after an + * individual failure. `throwOnValidationError: true` is critical: without + * it, Mongoose's `insertMany` silently drops documents that fail schema + * validation (e.g. a required field is missing) and reports success for + * the remaining valid docs — leaving the caller unable to record per-object + * import errors. With the flag, Mongoose throws a `MongooseBulkWriteError` + * after attempting the valid docs, carrying both the validation errors and + * the `results` array we use to map each failure back to its source index. * * Discriminator-aware: each child model's `insertMany` sets the correct * `__t` discriminator key automatically, so callers should invoke this on @@ -413,17 +420,51 @@ class BaseRepository extends AbstractRepository { * @param {Object} [options] * @param {boolean} [options.ordered=false] - Stop on first error if true * @returns {Promise<{ inserted: Array, errors: Array<{ index, message, code }> }>} + * `errors[].index` is the index into the input `dataArr`; the caller can + * use it to recover the original document for error reporting. */ async saveMany(dataArr, { ordered = false } = {}) { if (!Array.isArray(dataArr) || dataArr.length === 0) { return { inserted: [], errors: [] }; } try { - const inserted = await this.model.insertMany(dataArr, { ordered }); + const inserted = await this.model.insertMany(dataArr, { + ordered, + throwOnValidationError: true, + }); return { inserted, errors: [] }; } catch (err) { - // Mongoose BulkWriteError surfaces partial success when ordered:false. - // Successful inserts are on err.insertedDocs; failures on err.writeErrors. + // MongooseBulkWriteError: one or more docs failed Mongoose schema + // validation. `err.results` mirrors the input order — successfully + // inserted entries are Mongoose documents (identifiable by `_id`), + // while failures are the original input objects (no `_id`). Walking + // the results in order, the k-th failure corresponds to + // `err.validationErrors[k]` (Mongoose pre-sorts validationErrors by + // source index). + if (err?.name === 'MongooseBulkWriteError') { + const errors = []; + const inserted = []; + const validationErrors = err.validationErrors || []; + const results = err.results || []; + let veIdx = 0; + for (let i = 0; i < results.length; i++) { + const r = results[i]; + if (r && r._id) { + inserted.push(r); + } else { + const ve = validationErrors[veIdx++]; + errors.push({ + index: i, + message: ve?.message ?? 'Mongoose validation error', + code: ve?.name || 'ValidationError', + }); + } + } + return { inserted, errors }; + } + // MongoDB driver-side failure (e.g., duplicate-key race). Per-doc + // errors are on `err.writeErrors`; successful inserts on + // `err.insertedDocs`. if (err?.name === 'MongoBulkWriteError' || err?.writeErrors) { const errors = (err.writeErrors || []).map((we) => ({ index: we.index ?? we.err?.index, From ac2d803695e56763dc3a224b4362fe027638a4fc Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:34:19 -0400 Subject: [PATCH 341/370] fix(logging): interpolate %s placeholders and unify validation error logs The winston logger was never configured with format.splat(), so printf-style %s placeholders were never interpolated -- 'Bad request: %s' logged literally and the payload (including ADM validation details) was dropped. Add splat() to a shared base format applied in both format modes. Also collapse requestValidation's two log lines (a bare message plus a raw JSON.stringify(err) blob) into a single labeled 'Request failed validation: %s' line, consistent with the other error handlers. --- app/lib/error-handler.js | 3 +-- app/lib/logger.js | 18 +++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index c7d30e31..db79a262 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -53,8 +53,7 @@ exports.bodyParser = function (err, req, res, next) { exports.requestValidation = function (err, req, res, next) { if (err.status && err.message) { - logger.warn('Request failed validation'); - logger.info(JSON.stringify(err)); + logger.warn('Request failed validation: %s', JSON.stringify(err)); res.status(err.status).send(err.message); } else { next(err); diff --git a/app/lib/logger.js b/app/lib/logger.js index 8597c750..31ebf254 100644 --- a/app/lib/logger.js +++ b/app/lib/logger.js @@ -12,17 +12,21 @@ const logLevels = { debug: 5, }; +// Shared formats applied in every mode. `splat()` enables printf-style +// interpolation (e.g. logger.warn('Bad request: %s', body)); without it winston +// leaves the `%s` token uninterpolated and drops the extra argument. +const baseFormats = [ + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.splat(), +]; + // Use detailed format for debug/verbose levels, cleaner one-liner format otherwise const consoleFormat = config.logging.logLevel === 'debug' || config.logging.logLevel === 'verbose' - ? winston.format.combine( - winston.format.timestamp(), - winston.format.errors({ stack: true }), - winston.format.prettyPrint(), - ) + ? winston.format.combine(...baseFormats, winston.format.prettyPrint()) : winston.format.combine( - winston.format.timestamp(), - winston.format.errors({ stack: true }), + ...baseFormats, winston.format.printf( (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, ), From ede05c1664be0deda94a52aafd6ecf1d5ea4090a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 18 Jun 2026 12:31:00 -0400 Subject: [PATCH 342/370] test: inline small JSON fixtures into their spec files Small single-object/data-driven test fixtures were inconsistently split into separate .json files and loaded via readJson/require. Inline them so the payload under test is visible at rest in the spec, matching the dominant convention (~55 specs already inline their fixtures). Large STIX collection bundles remain external. - groups.query.json -> baseGroup const in groups.query.spec.js - teams.invalid.json -> inline array in teams-invalid.spec.js - user-accounts.invalid.json -> inline array in user-accounts-invalid.spec.js - delete teams.valid.json and user-accounts.valid.json (orphaned, no consumers) --- app/tests/api/groups/groups.query.json | 14 ----- app/tests/api/groups/groups.query.spec.js | 25 ++++++--- app/tests/api/teams/teams-invalid.spec.js | 11 +++- app/tests/api/teams/teams.invalid.json | 6 --- app/tests/api/teams/teams.valid.json | 7 --- .../user-accounts-invalid.spec.js | 21 +++++++- .../user-accounts/user-accounts.invalid.json | 53 ------------------- .../user-accounts/user-accounts.valid.json | 33 ------------ 8 files changed, 45 insertions(+), 125 deletions(-) delete mode 100644 app/tests/api/groups/groups.query.json delete mode 100644 app/tests/api/teams/teams.invalid.json delete mode 100644 app/tests/api/teams/teams.valid.json delete mode 100644 app/tests/api/user-accounts/user-accounts.invalid.json delete mode 100644 app/tests/api/user-accounts/user-accounts.valid.json diff --git a/app/tests/api/groups/groups.query.json b/app/tests/api/groups/groups.query.json deleted file mode 100644 index 82bd3eeb..00000000 --- a/app/tests/api/groups/groups.query.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "workspace": { - "workflow": {} - }, - "stix": { - "spec_version": "2.1", - "type": "intrusion-set", - "description": "This is a group.", - "external_references": [], - "object_marking_refs": ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0" - } -} diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 86d87c9a..5fcea5af 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -1,5 +1,3 @@ -const fs = require('fs').promises; - const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); @@ -17,15 +15,27 @@ const databaseConfiguration = require('../../../lib/database-configuration'); const userAccountsService = require('../../../services/system/user-accounts-service'); const groupsService = require('../../../services/stix/groups-service'); +// Base group used to derive all of the seeded query fixtures. Each created group +// deep-clones this and overrides only the fields a given test cares about. +const baseGroup = { + workspace: { + workflow: {}, + }, + stix: { + spec_version: '2.1', + type: 'intrusion-set', + description: 'This is a group.', + external_references: [], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + x_mitre_version: '1.0', + }, +}; + function asyncWait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -async function readJson(path) { - const data = await fs.readFile(require.resolve(path)); - return JSON.parse(data); -} - async function configureAndLoadGroups(baseGroup, userAccountId1, userAccountId2) { // Helper: create a group from config async function createGroup(overrides, userAccountId) { @@ -157,7 +167,6 @@ describe('Groups API Queries', function () { userAccount1 = await userAccountsService.create(userAccountData1); userAccount2 = await userAccountsService.create(userAccountData2); - const baseGroup = await readJson('./groups.query.json'); await configureAndLoadGroups(baseGroup, userAccount1.id, userAccount2.id); }); diff --git a/app/tests/api/teams/teams-invalid.spec.js b/app/tests/api/teams/teams-invalid.spec.js index 148e8f0e..fe168557 100644 --- a/app/tests/api/teams/teams-invalid.spec.js +++ b/app/tests/api/teams/teams-invalid.spec.js @@ -7,10 +7,17 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const teams = require('./teams.invalid.json'); - const login = require('../../shared/login'); +// Invalid team payloads — each is missing a required field. Used to assert the +// API rejects malformed input with a 400. +const teams = [ + { + description: 'no name', + userIDs: [], + }, +]; + describe('Teams API Test Invalid Data', function () { let app; let passportCookie; diff --git a/app/tests/api/teams/teams.invalid.json b/app/tests/api/teams/teams.invalid.json deleted file mode 100644 index 330ffc3f..00000000 --- a/app/tests/api/teams/teams.invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "description": "no name", - "userIDs": [] - } -] diff --git a/app/tests/api/teams/teams.valid.json b/app/tests/api/teams/teams.valid.json deleted file mode 100644 index 8694fdeb..00000000 --- a/app/tests/api/teams/teams.valid.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "name": "team1", - "description": "exampleTeam", - "userIDs": [] - } -] diff --git a/app/tests/api/user-accounts/user-accounts-invalid.spec.js b/app/tests/api/user-accounts/user-accounts-invalid.spec.js index ab30c81f..fe0a118e 100644 --- a/app/tests/api/user-accounts/user-accounts-invalid.spec.js +++ b/app/tests/api/user-accounts/user-accounts-invalid.spec.js @@ -7,10 +7,27 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const userAccounts = require('./user-accounts.invalid.json'); - const login = require('../../shared/login'); +// Invalid user account payloads — each violates a required-field, enum, type, or +// business rule. Used to assert the API rejects malformed input with a 400. +const userAccounts = [ + { email: 'user1@test.com', username: 'user missing status and role' }, + { email: 'user1@test.com', displayName: 'user missing username', status: 'pending' }, + { email: 'user2@test.com', username: 'user invalid status', status: 'abcde', role: 'editor' }, + { email: 'user3@test.com', username: 'user invalid role', status: 'active', role: 'xyzzy' }, + { + email: 'user4@test.com', + username: 'user inactive cannot have role', + status: 'inactive', + role: 'admin', + }, + { email: 5, username: 'user has number for email', status: 'active', role: 'editor' }, + { email: 'user6@test.com', username: 6, status: 'active', role: 'editor' }, + { email: 'user7@test.com', username: 'user has number for status', status: 7, role: 'editor' }, + { email: 'user8@test.com', username: 'user has number for role', status: 'active', role: 8 }, +]; + describe('User Accounts API Test Invalid Data', function () { let app; let passportCookie; diff --git a/app/tests/api/user-accounts/user-accounts.invalid.json b/app/tests/api/user-accounts/user-accounts.invalid.json deleted file mode 100644 index 45ac12dd..00000000 --- a/app/tests/api/user-accounts/user-accounts.invalid.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "email": "user1@test.com", - "username": "user missing status and role" - }, - { - "email": "user1@test.com", - "displayName": "user missing username", - "status": "pending" - }, - { - "email": "user2@test.com", - "username": "user invalid status", - "status": "abcde", - "role": "editor" - }, - { - "email": "user3@test.com", - "username": "user invalid role", - "status": "active", - "role": "xyzzy" - }, - { - "email": "user4@test.com", - "username": "user inactive cannot have role", - "status": "inactive", - "role": "admin" - }, - { - "email": 5, - "username": "user has number for email", - "status": "active", - "role": "editor" - }, - { - "email": "user6@test.com", - "username": 6, - "status": "active", - "role": "editor" - }, - { - "email": "user7@test.com", - "username": "user has number for status", - "status": 7, - "role": "editor" - }, - { - "email": "user8@test.com", - "username": "user has number for role", - "status": "active", - "role": 8 - } -] diff --git a/app/tests/api/user-accounts/user-accounts.valid.json b/app/tests/api/user-accounts/user-accounts.valid.json deleted file mode 100644 index 9ac791a6..00000000 --- a/app/tests/api/user-accounts/user-accounts.valid.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "email": "user1@test.com", - "username": "user1@test.com", - "displayName": "User 1", - "status": "active", - "role": "visitor" - }, - { - "email": "user2@test.com", - "username": "user2@test.com", - "displayName": "User 2", - "status": "active", - "role": "editor" - }, - { - "email": "user3@test.com", - "username": "user3@test.com", - "displayName": "User 3", - "status": "active", - "role": "admin" - }, - { - "email": "user4@test.com", - "username": "user4", - "status": "inactive" - }, - { - "email": "user5@test.com", - "username": "user5", - "status": "pending" - } -] From b4157275b702af1c80c69d121f8edb5eb6c260f6 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:57:02 -0400 Subject: [PATCH 343/370] test(techniques): run technique suites with ADM validation enabled Enable config.validateRequests.withAttackDataModel across all six technique specs and make the seeded payloads ADM-compliant (valid kill_chain_name and x_mitre_platforms enums; drop non-compliant, non-asserted fields such as x_mitre_impact_type and x_mitre_data_sources). - techniques.spec.js / .query / .revoke / -pagination: ADM-compliant fixtures - techniques.convert: flag flip only (fixture already compliant) - techniques.tactics: flag flip; bundle is imported via the fidelity-tolerant import path, which records per-object ADM issues rather than rejecting them - techniques.query.json inlined into the spec (single-object fixture) - pagination: add a validateWithAdm option so the suite pins ADM state itself instead of inheriting the shared config singleton from a prior spec --- .../techniques/techniques-pagination.spec.js | 9 ++-- .../api/techniques/techniques.convert.spec.js | 2 +- .../api/techniques/techniques.query.json | 19 --------- .../api/techniques/techniques.query.spec.js | 42 +++++++++++++------ .../api/techniques/techniques.revoke.spec.js | 6 +-- app/tests/api/techniques/techniques.spec.js | 17 ++++---- .../api/techniques/techniques.tactics.spec.js | 8 +++- app/tests/shared/pagination.js | 11 +++++ 8 files changed, 65 insertions(+), 49 deletions(-) delete mode 100644 app/tests/api/techniques/techniques.query.json diff --git a/app/tests/api/techniques/techniques-pagination.spec.js b/app/tests/api/techniques/techniques-pagination.spec.js index 4ca8934d..19fea222 100644 --- a/app/tests/api/techniques/techniques-pagination.spec.js +++ b/app/tests/api/techniques/techniques-pagination.spec.js @@ -16,12 +16,10 @@ const initialObjectData = { // Removed external_references - backend generates ATT&CK IDs and external references object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], - x_mitre_data_sources: ['data-source-1', 'data-source-2'], + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, - x_mitre_impact_type: ['impact-1'], - x_mitre_platforms: ['platform-1', 'platform-2'], + x_mitre_platforms: ['Linux', 'macOS'], }, }; @@ -29,6 +27,9 @@ const options = { prefix: 'attack-pattern', baseUrl: '/api/techniques', label: 'Techniques', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(techniquesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/techniques/techniques.convert.spec.js b/app/tests/api/techniques/techniques.convert.spec.js index 8798ed42..f900b696 100644 --- a/app/tests/api/techniques/techniques.convert.spec.js +++ b/app/tests/api/techniques/techniques.convert.spec.js @@ -36,7 +36,7 @@ describe('Techniques Convert API', function () { await database.initializeConnection(); await databaseConfiguration.checkSystemConfiguration(); - config.validateRequests.withAttackDataModel = false; + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = false; app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.query.json b/app/tests/api/techniques/techniques.query.json deleted file mode 100644 index fb6d51f2..00000000 --- a/app/tests/api/techniques/techniques.query.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "workspace": { - "workflow": {} - }, - "stix": { - "spec_version": "2.1", - "type": "attack-pattern", - "description": "This is a technique.", - "external_references": [{ "source_name": "source-1", "external_id": "s1" }], - "object_marking_refs": ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "kill_chain_phases": [{ "kill_chain_name": "kill-chain-name-1", "phase_name": "phase-1" }], - "x_mitre_data_sources": ["data-source-1", "data-source-2"], - "x_mitre_detection": "detection text", - "x_mitre_is_subtechnique": false, - "x_mitre_impact_type": ["impact-1"], - "x_mitre_platforms": ["platform-1", "platform-2"] - } -} diff --git a/app/tests/api/techniques/techniques.query.spec.js b/app/tests/api/techniques/techniques.query.spec.js index 7e83f092..ad0a02d3 100644 --- a/app/tests/api/techniques/techniques.query.spec.js +++ b/app/tests/api/techniques/techniques.query.spec.js @@ -1,5 +1,3 @@ -const fs = require('fs').promises; - const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); @@ -16,15 +14,33 @@ const databaseConfiguration = require('../../../lib/database-configuration'); const techniquesService = require('../../../services/stix/techniques-service'); +// Base technique used to derive all of the seeded query fixtures. Each created +// technique deep-clones this and overrides only the fields a given test cares +// about (deprecated/revoked status, workflow state, domain, platform). +const baseTechnique = { + workspace: { + workflow: {}, + }, + stix: { + spec_version: '2.1', + type: 'attack-pattern', + description: 'This is a technique.', + external_references: [{ source_name: 'source-1', external_id: 's1' }], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_detection: 'detection text', + x_mitre_is_subtechnique: false, + x_mitre_version: '1.0', + x_mitre_domains: ['enterprise-attack'], + x_mitre_platforms: ['Linux', 'macOS'], + }, +}; + function asyncWait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -async function readJson(path) { - const data = await fs.readFile(require.resolve(path)); - return JSON.parse(data); -} - async function configureAndLoadTechniques(baseTechnique) { // Helper: create a technique from config async function createTechnique(overrides) { @@ -49,12 +65,13 @@ async function configureAndLoadTechniques(baseTechnique) { // technique 1: x_mitre_deprecated,revoked undefined, state undefined const technique1 = await createTechnique({}); - // technique 2: x_mitre_deprecated = false, state = work-in-progress, mobile-attack domain + // technique 2: x_mitre_deprecated = false, state = work-in-progress, mobile-attack domain. + // Adds a unique platform ('Windows') so the platform-filter test can target it. await createTechnique({ stix: { x_mitre_deprecated: false, x_mitre_domains: ['mobile-attack'], - x_mitre_platforms: [...baseTechnique.stix.x_mitre_platforms, 'platform-3'], + x_mitre_platforms: [...baseTechnique.stix.x_mitre_platforms, 'Windows'], }, workspace: { workflow: { state: 'work-in-progress' } }, }); @@ -136,14 +153,13 @@ describe('Techniques Query API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the seeded fixtures below are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app app = await require('../../../index').initializeApp(); - const baseTechnique = await readJson('./techniques.query.json'); await configureAndLoadTechniques(baseTechnique); // Log into the app @@ -286,7 +302,7 @@ describe('Techniques Query API', function () { it('GET /api/techniques should return techniques containing the platform', async function () { const res = await request(app) - .get('/api/techniques?platform=platform-3') + .get('/api/techniques?platform=Windows') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js index a691b266..54cb4859 100644 --- a/app/tests/api/techniques/techniques.revoke.spec.js +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -24,9 +24,9 @@ const initialObjectData = { description: 'This technique will be revoked.', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], x_mitre_is_subtechnique: false, - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Linux'], }, }; @@ -38,7 +38,7 @@ describe('Techniques Revoke API', function () { await database.initializeConnection(); await databaseConfiguration.checkSystemConfiguration(); - config.validateRequests.withAttackDataModel = false; + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.spec.js b/app/tests/api/techniques/techniques.spec.js index a5505fea..7bf95fdc 100644 --- a/app/tests/api/techniques/techniques.spec.js +++ b/app/tests/api/techniques/techniques.spec.js @@ -24,16 +24,19 @@ const initialObjectData = { spec_version: '2.1', type: 'attack-pattern', description: 'This is a technique. Orange.', - // external_references: [ + // external_references and stix.id are populated by the REST API object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], - x_mitre_modified_by_ref: 'identity--d6424da5-85a0-496e-ae17-494499271108', + // ADM requires a kill_chain_name from the ATT&CK enum. The `impact` tactic is + // required for x_mitre_impact_type to be permitted. + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'impact' }], + // ADM requires x_mitre_modified_by_ref to be the MITRE ATT&CK identity + x_mitre_modified_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', // x_mitre_data_sources: ['data-source-1', 'data-source-2'], // TODO field is deprecated x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, - x_mitre_impact_type: ['impact-1'], - x_mitre_platforms: ['platform-1', 'platform-2'], + x_mitre_impact_type: ['Availability'], + x_mitre_platforms: ['Linux'], x_mitre_network_requirements: true, }, }; @@ -50,8 +53,8 @@ describe('Techniques Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/techniques/techniques.tactics.spec.js b/app/tests/api/techniques/techniques.tactics.spec.js index bb0e1d05..02442162 100644 --- a/app/tests/api/techniques/techniques.tactics.spec.js +++ b/app/tests/api/techniques/techniques.tactics.spec.js @@ -30,8 +30,12 @@ describe('Techniques with Tactics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation. NOTE: this suite seeds via the collection-bundle + // import path, which records per-object ADM issues (import-fidelity contract) + // rather than rejecting them — so the bundle below is imported even though it + // is not fully ADM-compliant. This suite exercises the technique<->tactic + // relationship endpoints, not request-level ADM enforcement. + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/shared/pagination.js b/app/tests/shared/pagination.js index 8832d633..be3b28de 100644 --- a/app/tests/shared/pagination.js +++ b/app/tests/shared/pagination.js @@ -2,6 +2,7 @@ const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); +const config = require('../../config/config'); const logger = require('../../lib/logger'); logger.level = 'debug'; @@ -19,6 +20,9 @@ function PaginationTests(service, initialObjectAData, options) { prefix: options.prefix ?? 'test-object', baseUrl: options.baseUrl, label: options.label ?? 'TestObjects', + // Optional: pin ADM request validation for this suite. Left undefined, the + // suite inherits whatever the shared config singleton currently holds. + validateWithAdm: options.validateWithAdm, }; this.options.stateQuery = options.state ? `&state=${options.state}` : ''; @@ -51,6 +55,13 @@ PaginationTests.prototype.executeTests = function () { let passportCookie; before(async function () { + // Pin ADM validation state (when the caller specified one) so the suite + // does not depend on whichever spec ran last leaving the shared config + // singleton in a particular state. + if (self.options.validateWithAdm !== undefined) { + config.validateRequests.withAttackDataModel = self.options.validateWithAdm; + } + // Establish the database connection // Use an in-memory database that we spin up for the test await database.initializeConnection(); From a1f6f78b08f9e24623134cee12da3f0e4b2b4243 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:06:32 -0400 Subject: [PATCH 344/370] test(analytics): run analytics suites with ADM validation enabled Enable config.validateRequests.withAttackDataModel across the analytics specs and make the seeded payloads ADM-compliant: - x_mitre_platforms 'windows' -> 'Windows' (enum is case-sensitive) - drop 'description' from the detection-strategy fixture (not in its ADM schema) - replace placeholder non-UUID data-component STIX ids with valid type--uuidv4 values (kept consistent across reference and assertion); the non-existent-ref test uses a valid-format-but-absent UUID so ADM (400) does not preempt the intended 404 - omit an empty x_mitre_log_source_references array (schema requires it to be non-empty when present) - analytics-pagination: pin ADM via the validateWithAdm option --- .../analytics/analytics-includeRefs.spec.js | 30 ++++++++++++------- .../analytics/analytics-pagination.spec.js | 5 +++- app/tests/api/analytics/analytics.spec.js | 8 +++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index c1e98b03..d5e5094a 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -25,11 +25,11 @@ const analyticData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_log_source_references: [ { - x_mitre_data_component_ref: 'x-mitre-data-component--test-data-component-1', + x_mitre_data_component_ref: 'x-mitre-data-component--3d6c9f1b-7f8a-4f2e-9b1a-2c3d4e5f6a7b', name: 'perm-1', channel: 'perm-1', }, @@ -65,7 +65,7 @@ const dataComponentData = { }, }, stix: { - id: 'x-mitre-data-component--test-data-component-1', + id: 'x-mitre-data-component--3d6c9f1b-7f8a-4f2e-9b1a-2c3d4e5f6a7b', name: 'test-data-component', spec_version: '2.1', type: 'x-mitre-data-component', @@ -89,8 +89,8 @@ describe('Analytics API - includeRefs Parameter', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -115,7 +115,9 @@ describe('Analytics API - includeRefs Parameter', function () { createdDataComponent = res.body; expect(createdDataComponent).toBeDefined(); - expect(createdDataComponent.stix.id).toBe('x-mitre-data-component--test-data-component-1'); + expect(createdDataComponent.stix.id).toBe( + 'x-mitre-data-component--3d6c9f1b-7f8a-4f2e-9b1a-2c3d4e5f6a7b', + ); }); it('Setup: Create analytic for testing', async function () { @@ -318,7 +320,9 @@ describe('Analytics API - includeRefs Parameter', function () { stix: { ...analyticData.stix, name: 'analytic-without-refs', - x_mitre_log_source_references: [], + // Omit log source references entirely; the ADM schema requires the + // array to be non-empty when present. + x_mitre_log_source_references: undefined, created: new Date().toISOString(), modified: new Date().toISOString(), }, @@ -359,7 +363,9 @@ describe('Analytics API - includeRefs Parameter', function () { name: 'analytic-with-bad-ref', x_mitre_log_source_references: [ { - x_mitre_data_component_ref: 'x-mitre-data-component--non-existent', + // Valid STIX id format, but no such data component exists -> 404 in beforeCreate + x_mitre_data_component_ref: + 'x-mitre-data-component--ffffffff-ffff-4fff-8fff-ffffffffffff', name: 'perm-1', channel: 'perm-1', }, @@ -384,7 +390,7 @@ describe('Analytics API - includeRefs Parameter', function () { ...dataComponentData, stix: { ...dataComponentData.stix, - id: 'x-mitre-data-component--no-ext-refs', + id: 'x-mitre-data-component--a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', name: 'data-component-no-ext-refs', external_references: [], created: new Date().toISOString(), @@ -406,7 +412,8 @@ describe('Analytics API - includeRefs Parameter', function () { name: 'analytic-with-no-ext-ref-data-component', x_mitre_log_source_references: [ { - x_mitre_data_component_ref: 'x-mitre-data-component--no-ext-refs', + x_mitre_data_component_ref: + 'x-mitre-data-component--a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', name: 'perm-1', channel: 'perm-1', }, @@ -435,7 +442,8 @@ describe('Analytics API - includeRefs Parameter', function () { const analytic = analytics[0]; const dataComponentRef = analytic.workspace.embedded_relationships.find( (rel) => - rel.stix_id === 'x-mitre-data-component--no-ext-refs' && rel.direction === 'outbound', + rel.stix_id === 'x-mitre-data-component--a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' && + rel.direction === 'outbound', ); expect(dataComponentRef).toBeDefined(); // Even without external_references, the system assigns an attack_id diff --git a/app/tests/api/analytics/analytics-pagination.spec.js b/app/tests/api/analytics/analytics-pagination.spec.js index 7bd8fc37..56b841cd 100644 --- a/app/tests/api/analytics/analytics-pagination.spec.js +++ b/app/tests/api/analytics/analytics-pagination.spec.js @@ -17,7 +17,7 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -36,6 +36,9 @@ const options = { prefix: 'x-mitre-analytic', baseUrl: '/api/analytics', label: 'Analytics', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(analyticsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/analytics/analytics.spec.js b/app/tests/api/analytics/analytics.spec.js index fea3c096..4348e4ed 100644 --- a/app/tests/api/analytics/analytics.spec.js +++ b/app/tests/api/analytics/analytics.spec.js @@ -30,7 +30,7 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -57,6 +57,10 @@ describe('Analytics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); @@ -393,7 +397,7 @@ describe('Analytics API', function () { name: 'Network Connection Creation Detection Strategy', spec_version: '2.1', type: 'x-mitre-detection-strategy', - description: 'Strategy for detecting network connections', + // Note: the x-mitre-detection-strategy ADM schema does not define a description field object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', From e3767d02f97a61c645c61f9be6bee15f2371493a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:23:45 -0400 Subject: [PATCH 345/370] test(assets): run assets suite with ADM validation enabled Enable config.validateRequests.withAttackDataModel and replace placeholder sector strings with valid enum values (x_mitre_sectors and x_mitre_related_assets[].related_asset_sectors must be one of Electric, Water and Wastewater, Manufacturing, Rail, Maritime, General). --- app/tests/api/assets/assets.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/tests/api/assets/assets.spec.js b/app/tests/api/assets/assets.spec.js index 9d1db830..c8b9be55 100644 --- a/app/tests/api/assets/assets.spec.js +++ b/app/tests/api/assets/assets.spec.js @@ -24,16 +24,16 @@ const initialObjectData = { spec_version: '2.1', type: 'x-mitre-asset', description: 'This is an asset.', - x_mitre_sectors: ['sector placeholder 1'], + x_mitre_sectors: ['Electric'], x_mitre_related_assets: [ { name: 'related asset 1', - related_asset_sectors: ['related asset sector placeholder 1'], + related_asset_sectors: ['Water and Wastewater'], description: 'This is a related asset', }, { name: 'related asset 2', - related_asset_sectors: ['related asset sector placeholder 2'], + related_asset_sectors: ['Manufacturing'], description: 'This is another related asset', }, ], @@ -49,8 +49,8 @@ describe('Assets API', function () { let passportCookie; before(async function () { - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Establish the database connection From 436e142cb8ef0f78b43eca8131b6be04491315ad Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:36:50 -0400 Subject: [PATCH 346/370] test(campaigns): run campaigns suite with ADM validation enabled Flag flip only; the campaign and marking-definition fixtures were already ADM-compliant. --- app/tests/api/campaigns/campaigns.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/campaigns/campaigns.spec.js b/app/tests/api/campaigns/campaigns.spec.js index 30f21032..c500ecc8 100644 --- a/app/tests/api/campaigns/campaigns.spec.js +++ b/app/tests/api/campaigns/campaigns.spec.js @@ -86,8 +86,8 @@ describe('Campaigns API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 506d42eab5361826fe67c1fd6888f19f0735e33a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:57:20 -0400 Subject: [PATCH 347/370] test(collections): run collections suite with ADM validation enabled - add required x_mitre_version to the x-mitre-collection fixture (every ATT&CK domain object requires it; the full collection schema rejected its absence) - bundled malware fixture x_mitre_platforms 'platform-1' -> 'Android' --- app/tests/api/collections/collections.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/tests/api/collections/collections.spec.js b/app/tests/api/collections/collections.spec.js index c3dc9bed..55a1fdd9 100644 --- a/app/tests/api/collections/collections.spec.js +++ b/app/tests/api/collections/collections.spec.js @@ -39,6 +39,7 @@ const initialCollectionData = { // external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + x_mitre_version: '1.0', x_mitre_contents: [], }, }; @@ -96,7 +97,7 @@ const softwareData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_aliases: ['software-1'], - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-1', 'contributor-2'], x_mitre_domains: ['mobile-attack'], }, @@ -115,7 +116,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { await databaseConfiguration.checkSystemConfiguration(); // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From bcaeba50c6c0b58fc24184b9b1d358b7e24bb310 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:21:04 -0400 Subject: [PATCH 348/370] test(data-components): run data-components suites with ADM validation enabled Flag flip plus the pagination validateWithAdm option; fixtures were already ADM-compliant (work-in-progress / partial schema). --- .../api/data-components/data-components-pagination.spec.js | 3 +++ app/tests/api/data-components/data-components.spec.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/tests/api/data-components/data-components-pagination.spec.js b/app/tests/api/data-components/data-components-pagination.spec.js index 2ebfc4d8..30b0282e 100644 --- a/app/tests/api/data-components/data-components-pagination.spec.js +++ b/app/tests/api/data-components/data-components-pagination.spec.js @@ -33,6 +33,9 @@ const options = { prefix: 'x-mitre-data-component', baseUrl: '/api/data-components', label: 'Data Components', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(dataComponentsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index 520222b6..6637c8a0 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -55,8 +55,8 @@ describe('Data Components API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 3a41b593086e0482832c119aa55a8bd88ff34a83 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:30:40 -0400 Subject: [PATCH 349/370] test(data-sources): run data-sources suites with ADM validation enabled - x_mitre_collection_layers placeholders ['duis','laboris'] -> ['Host','Network'] (must be from the supported collection-layers enum) - data-sources-pagination: pin ADM via the validateWithAdm option --- app/tests/api/data-sources/data-sources-pagination.spec.js | 3 +++ app/tests/api/data-sources/data-sources.spec.js | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/tests/api/data-sources/data-sources-pagination.spec.js b/app/tests/api/data-sources/data-sources-pagination.spec.js index 436d283f..a63fcf62 100644 --- a/app/tests/api/data-sources/data-sources-pagination.spec.js +++ b/app/tests/api/data-sources/data-sources-pagination.spec.js @@ -23,6 +23,9 @@ const options = { prefix: 'x-mitre-data-source', baseUrl: '/api/data-sources', label: 'Data Sources', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(dataSourcesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index 27aee363..3cdf4c07 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -33,7 +33,7 @@ const initialDataSourceData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_platforms: ['macOS', 'Office Suite', 'Identity Provider', 'Linux', 'Network Devices'], - x_mitre_collection_layers: ['duis', 'laboris'], + x_mitre_collection_layers: ['Host', 'Network'], x_mitre_contributors: ['Herbert Examplecontributor'], x_mitre_domains: ['enterprise-attack'], x_mitre_modified_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', @@ -109,8 +109,8 @@ describe('Data Sources API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From ccb2f5b5025c9e340617051ee98e442e22b6cd31 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:07:32 -0400 Subject: [PATCH 350/370] test(detection-strategies): run suites with ADM validation enabled - replace invalid-v4 analytic STIX ids (version nibble must be 4) with valid UUIDv4 values, kept consistent across the analytic definitions and the detection strategy's x_mitre_analytic_refs - x_mitre_platforms 'windows' -> 'Windows' on the seeded analytics - detection-strategies-pagination: pin ADM via the validateWithAdm option --- .../detection-strategies-pagination.spec.js | 3 +++ .../detection-strategies-spec.js | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js index e8946a9e..97c57055 100644 --- a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js @@ -30,6 +30,9 @@ const options = { prefix: 'x-mitre-detection-strategy', baseUrl: '/api/detection-strategies', label: 'Detection Strategies', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(detectionStrategiesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index 72595866..3c041f8e 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -30,8 +30,8 @@ const initialPostContentForDetectionStrategy = { x_mitre_version: '1.0', x_mitre_domains: ['enterprise-attack'], x_mitre_analytic_refs: [ - 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match! - 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', // must match! + 'x-mitre-analytic--12345678-1234-4234-8234-123456789000', // must match! + 'x-mitre-analytic--12345678-1234-4234-8234-123456789012', // must match! ], }, }; @@ -43,7 +43,7 @@ const initialPostContentForAnalytic1 = { }, }, stix: { - id: 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match the analytic's embedded ref! + id: 'x-mitre-analytic--12345678-1234-4234-8234-123456789000', // must match the analytic's embedded ref! name: 'analytic-1', spec_version: '2.1', type: 'x-mitre-analytic', @@ -52,7 +52,7 @@ const initialPostContentForAnalytic1 = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -74,7 +74,7 @@ const initialPostContentForAnalytic2 = { }, }, stix: { - id: 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', // must match the analytic's embedded ref! + id: 'x-mitre-analytic--12345678-1234-4234-8234-123456789012', // must match the analytic's embedded ref! name: 'analytic-2', spec_version: '2.1', type: 'x-mitre-analytic', @@ -83,7 +83,7 @@ const initialPostContentForAnalytic2 = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -110,8 +110,8 @@ describe('Detection Strategies API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 974d958380691eb054fa882ead9ecebe0030de06 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:26:33 -0400 Subject: [PATCH 351/370] test(groups): run groups suites with ADM validation enabled Enable ADM request validation in the groups CRUD, query, and input-validation specs. Pin the pagination harness to ADM validation and add enterprise-attack domains to the full-schema query fixture. --- app/tests/api/groups/groups-input-validation.spec.js | 4 ++-- app/tests/api/groups/groups-pagination.spec.js | 3 +++ app/tests/api/groups/groups.query.spec.js | 5 +++-- app/tests/api/groups/groups.spec.js | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/tests/api/groups/groups-input-validation.spec.js b/app/tests/api/groups/groups-input-validation.spec.js index f3bbbd25..a1f306c8 100644 --- a/app/tests/api/groups/groups-input-validation.spec.js +++ b/app/tests/api/groups/groups-input-validation.spec.js @@ -94,8 +94,8 @@ describe('Groups API Input Validation', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/groups/groups-pagination.spec.js b/app/tests/api/groups/groups-pagination.spec.js index 5dcdf283..4f399540 100644 --- a/app/tests/api/groups/groups-pagination.spec.js +++ b/app/tests/api/groups/groups-pagination.spec.js @@ -23,6 +23,9 @@ const options = { prefix: 'intrustion-set', baseUrl: '/api/groups', label: 'Groups', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(groupsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 5fcea5af..59771324 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -29,6 +29,7 @@ const baseGroup = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', + x_mitre_domains: ['enterprise-attack'], }, }; @@ -154,8 +155,8 @@ describe('Groups API Queries', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/groups/groups.spec.js b/app/tests/api/groups/groups.spec.js index 17902cf0..c8d7fd3e 100644 --- a/app/tests/api/groups/groups.spec.js +++ b/app/tests/api/groups/groups.spec.js @@ -80,8 +80,8 @@ describe('Groups API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 2983755dcee972ddc137f22520d96efc2972236c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:31:36 -0400 Subject: [PATCH 352/370] test(identities): run identities suites with ADM validation enabled Enable ADM request validation in the identities CRUD spec. Add an external reference to the seeded identity so full-schema update validation passes. --- app/tests/api/identities/identities.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/tests/api/identities/identities.spec.js b/app/tests/api/identities/identities.spec.js index b188211c..8c51c12e 100644 --- a/app/tests/api/identities/identities.spec.js +++ b/app/tests/api/identities/identities.spec.js @@ -25,6 +25,7 @@ const initialObjectData = { spec_version: '2.1', type: 'identity', description: 'This is an identity.', + external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], }, }; @@ -41,8 +42,8 @@ describe('Identity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 28eaa2cb7ccc124dc13a78ea030864bccbd5669b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:02:55 -0400 Subject: [PATCH 353/370] test(marking-definitions): run marking-definitions suites with ADM validation enabled Enable ADM request validation in the marking-definitions CRUD spec. No fixture changes were required; the existing WIP marking-definition payload is ADM-compliant. --- app/tests/api/marking-definitions/marking-definitions.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/marking-definitions/marking-definitions.spec.js b/app/tests/api/marking-definitions/marking-definitions.spec.js index 055fa8db..39c4d594 100644 --- a/app/tests/api/marking-definitions/marking-definitions.spec.js +++ b/app/tests/api/marking-definitions/marking-definitions.spec.js @@ -40,8 +40,8 @@ describe('Marking Definitions API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From ae2e2cae64a1855590f53accd1214b8a7c73c21e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:36:36 -0400 Subject: [PATCH 354/370] test(matrices): run matrices suites with ADM validation enabled Enable ADM request validation in the matrices CRUD spec. Use enterprise-attack as the seeded matrix domain so the server-composed matrix external reference is ADM-compliant. --- app/tests/api/matrices/matrices.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/tests/api/matrices/matrices.spec.js b/app/tests/api/matrices/matrices.spec.js index a02987c4..a77adfd4 100644 --- a/app/tests/api/matrices/matrices.spec.js +++ b/app/tests/api/matrices/matrices.spec.js @@ -40,7 +40,7 @@ const initialObjectData = { 'x-mitre-tactic--daa4cbb1-b4f4-4723-a824-7f1efd6e0592', 'x-mitre-tactic--d679bca2-e57d-4935-8650-8031c87a4400', ], - x_mitre_domains: ['mitre-attack'], + x_mitre_domains: ['enterprise-attack'], x_mitre_version: '1.0', }, }; @@ -60,8 +60,8 @@ describe('Matrices API', function () { // Initialize the express app app = await require('../../../index').initializeApp(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Log into the app From 2e06c028a92b3690225f6134be4a7e82a8aa44d0 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:56:00 -0400 Subject: [PATCH 355/370] test(mitigations): run mitigations suites with ADM validation enabled Enable ADM request validation in the mitigations CRUD spec. Pin the pagination harness to ADM validation; no fixture field changes were required. --- app/tests/api/mitigations/mitigations-pagination.spec.js | 3 +++ app/tests/api/mitigations/mitigations.spec.js | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/tests/api/mitigations/mitigations-pagination.spec.js b/app/tests/api/mitigations/mitigations-pagination.spec.js index f7eb5b6e..cf4ec5d8 100644 --- a/app/tests/api/mitigations/mitigations-pagination.spec.js +++ b/app/tests/api/mitigations/mitigations-pagination.spec.js @@ -24,6 +24,9 @@ const options = { prefix: 'course-of-action', baseUrl: '/api/mitigations', label: 'Mitigations', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(mitigationsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/mitigations/mitigations.spec.js b/app/tests/api/mitigations/mitigations.spec.js index 7d5b2da1..191de6f6 100644 --- a/app/tests/api/mitigations/mitigations.spec.js +++ b/app/tests/api/mitigations/mitigations.spec.js @@ -44,8 +44,8 @@ describe('Mitigations API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 8b1e9540f7f951538f4daccc90caacf2d15a370b Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:40:07 -0400 Subject: [PATCH 356/370] test(notes): run notes suites with ADM validation enabled Enable ADM request validation in the notes CRUD spec. No fixture changes were required; note objects do not currently have an ADM schema wired into request validation. --- app/tests/api/notes/notes.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/notes/notes.spec.js b/app/tests/api/notes/notes.spec.js index c31c7769..6493d747 100644 --- a/app/tests/api/notes/notes.spec.js +++ b/app/tests/api/notes/notes.spec.js @@ -44,8 +44,8 @@ describe('Notes API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 19a347b36f5a7533e6d7803323d671766f5871f4 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:17:02 -0400 Subject: [PATCH 357/370] test(recent-activity): run recent-activity suites with ADM validation enabled Enable ADM request validation in the recent-activity API spec. No request fixture changes were required; the suite seeds existing STIX bundle fixtures through the collection bundle importer. --- app/tests/api/recent-activity/recent-activity.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/recent-activity/recent-activity.spec.js b/app/tests/api/recent-activity/recent-activity.spec.js index 7f2a20cc..1c7e9819 100644 --- a/app/tests/api/recent-activity/recent-activity.spec.js +++ b/app/tests/api/recent-activity/recent-activity.spec.js @@ -30,8 +30,8 @@ describe('Recent Activity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this suite seeds existing STIX bundle fixtures + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 51ad6cc5ec2ebadf9925f6440e0c703fdc8957c5 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:19:06 -0400 Subject: [PATCH 358/370] test(references): run references suites with ADM validation enabled Enable ADM request validation in the references API spec for consistency. No fixture changes were required; references are system metadata records rather than STIX object request payloads. --- app/tests/api/references/references.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/references/references.spec.js b/app/tests/api/references/references.spec.js index b73df6d5..c4d2d08f 100644 --- a/app/tests/api/references/references.spec.js +++ b/app/tests/api/references/references.spec.js @@ -45,8 +45,8 @@ describe('References API', function () { // Wait until the Reference indexes are created await Reference.init(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation for consistency with STIX-object API suites + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From d07fe1add15af0e867d5f3b40e1690be0a5c9512 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:22:52 -0400 Subject: [PATCH 359/370] test(relationships): run relationships suites with ADM validation enabled Enable ADM request validation in the relationships CRUD and pagination specs. Pass validateWithAdm to the pagination harness. Remove the pagination helper's generated name from relationship fixtures because ADM does not allow name on relationship objects. Pin OpenAPI validation in the pagination spec so recursive runs initialize route validation consistently. --- .../relationships-pagination.spec.js | 16 +++++++++++++++- .../api/relationships/relationships.spec.js | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/tests/api/relationships/relationships-pagination.spec.js b/app/tests/api/relationships/relationships-pagination.spec.js index 194ebcc6..de2f5fd9 100644 --- a/app/tests/api/relationships/relationships-pagination.spec.js +++ b/app/tests/api/relationships/relationships-pagination.spec.js @@ -1,5 +1,8 @@ const relationshipsService = require('../../../services/stix/relationships-service'); const PaginationTests = require('../../shared/pagination'); +const config = require('../../../config/config'); + +config.validateRequests.withOpenApi = true; // modified and created properties will be set before calling REST API // stix.id property will be created by REST API @@ -28,6 +31,17 @@ const options = { prefix: 'relationship', baseUrl: '/api/relationships', label: 'Relationships', + validateWithAdm: true, +}; +const relationshipsPaginationService = { + async create(data, options) { + delete data.stix.name; + return relationshipsService.create(data, options); + }, }; -const paginationTests = new PaginationTests(relationshipsService, initialObjectData, options); +const paginationTests = new PaginationTests( + relationshipsPaginationService, + initialObjectData, + options, +); paginationTests.executeTests(); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 8c0884a3..a18e7f05 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -53,8 +53,8 @@ describe('Relationships API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 0c4202f9f47942b46daa75a4a196127d5a3b2720 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:24:17 -0400 Subject: [PATCH 360/370] test(reports): run reports suites with ADM validation enabled Enable ADM request validation in the reports API spec. No fixture changes were required; the WIP software and relationship setup payloads are ADM-compliant. --- app/tests/api/reports/reports.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/reports/reports.spec.js b/app/tests/api/reports/reports.spec.js index 0ea647bd..e3e696be 100644 --- a/app/tests/api/reports/reports.spec.js +++ b/app/tests/api/reports/reports.spec.js @@ -70,8 +70,8 @@ describe('Reports API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 3604275d60d7940d9d6a6f37a93b62673f28ae11 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:25:39 -0400 Subject: [PATCH 361/370] test(session): run session suites with ADM validation enabled Enable ADM request validation in the session API spec for consistency. No fixture changes were required; session requests are not STIX object payloads. --- app/tests/api/session/session.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/session/session.spec.js b/app/tests/api/session/session.spec.js index 01a05b60..3fccac23 100644 --- a/app/tests/api/session/session.spec.js +++ b/app/tests/api/session/session.spec.js @@ -18,8 +18,8 @@ describe('Session API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation for consistency with STIX-object API suites + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 136298b887d4adc774b97485bdbe0e8cfbdf095e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:27:18 -0400 Subject: [PATCH 362/370] test(software): run software suites with ADM validation enabled Enable ADM request validation in the software CRUD and pagination specs. Pass validateWithAdm to the pagination harness. Replace the synthetic platform fixture with Android so ADM platform validation passes. Pin OpenAPI validation in the pagination spec so recursive runs initialize route validation consistently. --- app/tests/api/software/software-pagination.spec.js | 6 +++++- app/tests/api/software/software.spec.js | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/tests/api/software/software-pagination.spec.js b/app/tests/api/software/software-pagination.spec.js index 2d9a038f..b0a5a2e4 100644 --- a/app/tests/api/software/software-pagination.spec.js +++ b/app/tests/api/software/software-pagination.spec.js @@ -1,5 +1,8 @@ const softwareService = require('../../../services/stix/software-service'); const PaginationTests = require('../../shared/pagination'); +const config = require('../../../config/config'); + +config.validateRequests.withOpenApi = true; // modified and created properties will be set before calling REST API // stix.id property will be created by REST API @@ -19,7 +22,7 @@ const initialObjectData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_aliases: ['software-1'], - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-1', 'contributor-2'], x_mitre_domains: ['mobile-attack'], }, @@ -29,6 +32,7 @@ const options = { prefix: 'software', baseUrl: '/api/software', label: 'Software', + validateWithAdm: true, }; const paginationTests = new PaginationTests(softwareService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/software/software.spec.js b/app/tests/api/software/software.spec.js index 2ceb91fe..d6894487 100644 --- a/app/tests/api/software/software.spec.js +++ b/app/tests/api/software/software.spec.js @@ -31,7 +31,7 @@ const initialObjectData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_aliases: ['software-1'], - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-1', 'contributor-2'], x_mitre_domains: ['mobile-attack'], }, @@ -61,8 +61,8 @@ describe('Software API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From f3d44520ba863dce5e771a9b71c2a19069345a4e Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:40:17 -0400 Subject: [PATCH 363/370] test(stix-bundles): run stix-bundles suites with ADM validation enabled Enable ADM request validation in both stix-bundles specs. Add valid v4 fixture IDs and required ADM metadata for the new-spec bundle. Normalize the legacy bundle's placeholder ATT&CK IDs, enum values, aliases, citations, and required version/modifier fields. --- .../api/stix-bundles/stix-bundles-old.spec.js | 120 +++++++++++ .../api/stix-bundles/stix-bundles.spec.js | 195 +++++++++++------- 2 files changed, 243 insertions(+), 72 deletions(-) diff --git a/app/tests/api/stix-bundles/stix-bundles-old.spec.js b/app/tests/api/stix-bundles/stix-bundles-old.spec.js index 649faf98..893ba0c3 100644 --- a/app/tests/api/stix-bundles/stix-bundles-old.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles-old.spec.js @@ -1,6 +1,7 @@ const request = require('supertest'); const { expect } = require('expect'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -629,6 +630,121 @@ const initialObjectData = { ], }; +function normalizeLegacyBundleFixtureForAdmImport(bundle) { + const counters = { + 'attack-pattern': 9000, + 'course-of-action': 9000, + malware: 9000, + 'intrusion-set': 9000, + 'x-mitre-data-source': 9000, + }; + + for (const stixObject of bundle.objects) { + if ( + Array.isArray(stixObject.external_references) && + stixObject.external_references.length === 0 && + stixObject.type !== 'intrusion-set' + ) { + delete stixObject.external_references; + } + + if (stixObject.type !== 'marking-definition' && stixObject.type !== 'note') { + stixObject.x_mitre_attack_spec_version = config.app.attackSpecVersion; + } + + switch (stixObject.type) { + case 'x-mitre-collection': + stixObject.x_mitre_version = '1.0'; + break; + + case 'identity': + break; + + case 'attack-pattern': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `T${counters[stixObject.type]}`; + stixObject.kill_chain_phases = stixObject.kill_chain_phases.map((phase) => ({ + ...phase, + kill_chain_name: stixObject.x_mitre_domains.includes(mobileDomain) + ? 'mitre-mobile-attack' + : stixObject.x_mitre_domains.includes(icsDomain) + ? 'mitre-ics-attack' + : 'mitre-attack', + phase_name: 'execution', + })); + stixObject.x_mitre_platforms = ['Windows', 'Linux']; + delete stixObject.x_mitre_impact_type; + break; + + case 'course-of-action': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `M${counters[stixObject.type]}`; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + break; + + case 'malware': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `S${counters[stixObject.type]}`; + stixObject.is_family = false; + stixObject.x_mitre_aliases = [stixObject.name, ...stixObject.x_mitre_aliases]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + stixObject.x_mitre_platforms = ['Windows']; + break; + + case 'intrusion-set': + counters[stixObject.type] += 1; + if ( + !Array.isArray(stixObject.external_references) || + stixObject.external_references.length === 0 + ) { + stixObject.external_references = [ + { source_name: 'mitre-attack', external_id: `G${counters[stixObject.type]}` }, + ]; + } + stixObject.aliases = [stixObject.name, ...stixObject.aliases]; + stixObject.x_mitre_domains = [enterpriseDomain]; + break; + + case 'campaign': + stixObject.aliases = [stixObject.name, ...stixObject.aliases]; + stixObject.external_references.push( + { source_name: 'Article 1', description: 'First seen citation.' }, + { source_name: 'Article 2', description: 'Last seen citation.' }, + ); + stixObject.revoked = false; + stixObject.x_mitre_domains = [enterpriseDomain]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + break; + + case 'relationship': + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + break; + + case 'x-mitre-data-source': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `DS${counters[stixObject.type]}`; + stixObject.description = `${stixObject.name} data source.`; + stixObject.x_mitre_collection_layers = ['Host']; + stixObject.x_mitre_domains = [enterpriseDomain]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + stixObject.x_mitre_version = '1.0'; + break; + + case 'x-mitre-data-component': + stixObject.description = `${stixObject.name} data component.`; + stixObject.x_mitre_domains = [enterpriseDomain]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + stixObject.x_mitre_version = '1.0'; + break; + + default: + break; + } + } +} + +normalizeLegacyBundleFixtureForAdmImport(initialObjectData); + // function printBundleCount(bundle) { // const count = { // techniques: 0, @@ -670,6 +786,10 @@ describe('STIX Bundles Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Enable ADM validation for the legacy bundle import path + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/stix-bundles/stix-bundles.spec.js b/app/tests/api/stix-bundles/stix-bundles.spec.js index fe0c43b6..98b5c656 100644 --- a/app/tests/api/stix-bundles/stix-bundles.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles.spec.js @@ -69,7 +69,7 @@ const login = require('../../shared/login'); const enterpriseDomain = 'enterprise-attack'; const icsDomain = 'ics-attack'; -const collectionId = 'x-mitre-collection--b0b12345-aaaa-bbbb-cccc-dddddddddddd'; +const collectionId = 'x-mitre-collection--b0b12345-aaaa-4bbb-8ccc-dddddddddddd'; const collectionTimestamp = new Date().toISOString(); const markingDefinitionId = 'marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'; @@ -104,65 +104,75 @@ const newSpecBundleData = { spec_version: '2.1', type: 'x-mitre-collection', description: 'Test collection for new ATT&CK specification features', - external_references: [], object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_version: '1.0', x_mitre_contents: [ // Techniques - { object_ref: 'attack-pattern--new-ent-001', object_modified: '2024-01-15T10:00:00.000Z' }, - { object_ref: 'attack-pattern--new-ent-002', object_modified: '2024-01-15T10:00:00.000Z' }, - { object_ref: 'attack-pattern--new-ics-001', object_modified: '2024-01-15T10:00:00.000Z' }, + { + object_ref: 'attack-pattern--11111111-1111-4111-8111-111111111111', + object_modified: '2024-01-15T10:00:00.000Z', + }, + { + object_ref: 'attack-pattern--22222222-2222-4222-8222-222222222222', + object_modified: '2024-01-15T10:00:00.000Z', + }, + { + object_ref: 'attack-pattern--33333333-3333-4333-8333-333333333333', + object_modified: '2024-01-15T10:00:00.000Z', + }, // Analytics { - object_ref: 'x-mitre-analytic--new-ana-001', + object_ref: 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-analytic--new-ana-002', + object_ref: 'x-mitre-analytic--55555555-5555-4555-8555-555555555555', object_modified: '2024-01-15T10:00:00.000Z', }, // Detection Strategies { - object_ref: 'x-mitre-detection-strategy--new-ds-001', + object_ref: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-detection-strategy--new-ds-002', + object_ref: 'x-mitre-detection-strategy--77777777-7777-4777-8777-777777777777', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-detection-strategy--new-ds-003', + object_ref: 'x-mitre-detection-strategy--88888888-8888-4888-8888-888888888888', object_modified: '2024-01-15T10:00:00.000Z', }, // Data Components { - object_ref: 'x-mitre-data-component--new-dc-001', + object_ref: 'x-mitre-data-component--99999999-9999-4999-8999-999999999999', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-data-component--new-dc-002', + object_ref: 'x-mitre-data-component--aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', object_modified: '2024-01-15T10:00:00.000Z', }, // Data Sources { - object_ref: 'x-mitre-data-source--new-ds-src-001', + object_ref: 'x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-data-source--new-ds-src-002', + object_ref: 'x-mitre-data-source--cccccccc-cccc-4ccc-8ccc-cccccccccccc', object_modified: '2024-01-15T10:00:00.000Z', }, // Relationships { - object_ref: 'relationship--new-ds-detects-tech-001', + object_ref: 'relationship--dddddddd-dddd-4ddd-8ddd-dddddddddddd', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'relationship--new-ds-detects-tech-002', + object_ref: 'relationship--eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'relationship--new-dc-detects-tech-dep', + object_ref: 'relationship--ffffffff-ffff-4fff-8fff-ffffffffffff', object_modified: '2024-01-15T10:00:00.000Z', }, // Supporting objects (identity and marking-definition should also be in x_mitre_contents) @@ -176,7 +186,7 @@ const newSpecBundleData = { // ======================================== { type: 'attack-pattern', - id: 'attack-pattern--new-ent-001', + id: 'attack-pattern--11111111-1111-4111-8111-111111111111', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Technique 1', @@ -186,13 +196,14 @@ const newSpecBundleData = { created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'T9001' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], x_mitre_version: '1.0', x_mitre_is_subtechnique: false, }, { type: 'attack-pattern', - id: 'attack-pattern--new-ent-002', + id: 'attack-pattern--22222222-2222-4222-8222-222222222222', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Technique 2', @@ -202,13 +213,14 @@ const newSpecBundleData = { created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'T9002' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'persistence' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], x_mitre_version: '1.0', x_mitre_is_subtechnique: false, }, { type: 'attack-pattern', - id: 'attack-pattern--new-ics-001', + id: 'attack-pattern--33333333-3333-4333-8333-333333333333', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'ICS Technique 1', @@ -218,6 +230,7 @@ const newSpecBundleData = { created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'T9003' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain, icsDomain], x_mitre_version: '1.0', x_mitre_is_subtechnique: false, @@ -228,7 +241,7 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-analytic', - id: 'x-mitre-analytic--new-ana-001', + id: 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Process Execution Analytic', @@ -239,16 +252,18 @@ const newSpecBundleData = { external_references: [ { source_name: 'mitre-attack', - external_id: 'ANA-001', - url: 'https://attack.mitre.org/detectionstrategies/DS-002#ANA-001', + external_id: 'AN0001', + url: 'https://attack.mitre.org/detectionstrategies/DET0002#AN0001', }, ], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], + x_mitre_platforms: ['Windows'], x_mitre_version: '1.0', }, { type: 'x-mitre-analytic', - id: 'x-mitre-analytic--new-ana-002', + id: 'x-mitre-analytic--55555555-5555-4555-8555-555555555555', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Persistence Mechanism Analytic', @@ -256,9 +271,11 @@ const newSpecBundleData = { spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'ANA-002' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'AN0002' }], // Note: No URL, meaning it's not attached to a detection strategy, meaning we don't want it in the bundle + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], + x_mitre_platforms: ['Windows'], x_mitre_version: '1.0', }, @@ -267,46 +284,52 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-detection-strategy', - id: 'x-mitre-detection-strategy--new-ds-001', + id: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Detection Strategy 1 - Detects Technique via Relationship', - description: 'This detection strategy detects attack-pattern--new-ent-001', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS-001' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'DET0001' }], + x_mitre_analytic_refs: ['x-mitre-analytic--44444444-4444-4444-8444-444444444444'], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - // Note: No x_mitre_domains - this is inferred from relationships }, { type: 'x-mitre-detection-strategy', - id: 'x-mitre-detection-strategy--new-ds-002', + id: 'x-mitre-detection-strategy--77777777-7777-4777-8777-777777777777', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Detection Strategy 2 - References Analytic', - description: 'This detection strategy references x-mitre-analytic--new-ana-001', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS-002' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'DET0002' }], + x_mitre_analytic_refs: ['x-mitre-analytic--44444444-4444-4444-8444-444444444444'], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - x_mitre_analytic_refs: ['x-mitre-analytic--new-ana-001'], - // Note: No x_mitre_domains - this is inferred from analytic refs }, { type: 'x-mitre-detection-strategy', - id: 'x-mitre-detection-strategy--new-ds-003', + id: 'x-mitre-detection-strategy--88888888-8888-4888-8888-888888888888', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Detection Strategy 3 - Not Included (orphaned)', - description: 'This detection strategy should NOT be included - no technique or analytic', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS-003' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'DET0003' }], + x_mitre_analytic_refs: ['x-mitre-analytic--55555555-5555-4555-8555-555555555555'], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - // Note: No detects relationship, no analytic refs, so this should NOT appear in bundle + // Note: No detects relationship, and its analytic lacks a URL, so this should NOT appear in bundle }, // ======================================== @@ -314,7 +337,7 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-data-component', - id: 'x-mitre-data-component--new-dc-001', + id: 'x-mitre-data-component--99999999-9999-4999-8999-999999999999', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Data Component', @@ -323,13 +346,15 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DC9001' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - x_mitre_data_source_ref: 'x-mitre-data-source--new-ds-src-001', + x_mitre_data_source_ref: 'x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', }, { type: 'x-mitre-data-component', - id: 'x-mitre-data-component--new-dc-002', + id: 'x-mitre-data-component--aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'ICS Data Component', @@ -338,9 +363,11 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DC9002' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [icsDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - x_mitre_data_source_ref: 'x-mitre-data-source--new-ds-src-002', + x_mitre_data_source_ref: 'x-mitre-data-source--cccccccc-cccc-4ccc-8ccc-cccccccccccc', }, // ======================================== @@ -348,7 +375,7 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-data-source', - id: 'x-mitre-data-source--new-ds-src-001', + id: 'x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Data Source', @@ -357,12 +384,15 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DS9001' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_collection_layers: ['Host'], x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', }, { type: 'x-mitre-data-source', - id: 'x-mitre-data-source--new-ds-src-002', + id: 'x-mitre-data-source--cccccccc-cccc-4ccc-8ccc-cccccccccccc', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'ICS Data Source', @@ -371,7 +401,10 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DS9002' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_collection_layers: ['Host'], x_mitre_domains: [icsDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', }, @@ -382,49 +415,52 @@ const newSpecBundleData = { // Valid 'detects' relationship: Detection Strategy → Technique { type: 'relationship', - id: 'relationship--new-ds-detects-tech-001', + id: 'relationship--dddddddd-dddd-4ddd-8ddd-dddddddddddd', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', relationship_type: 'detects', - source_ref: 'x-mitre-detection-strategy--new-ds-001', - target_ref: 'attack-pattern--new-ent-001', + source_ref: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', + target_ref: 'attack-pattern--11111111-1111-4111-8111-111111111111', description: 'Detection strategy detects enterprise technique 1', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_modified_by_ref: mitreIdentityId, }, // Valid 'detects' relationship: Detection Strategy → Technique (different technique) { type: 'relationship', - id: 'relationship--new-ds-detects-tech-002', + id: 'relationship--eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', relationship_type: 'detects', - source_ref: 'x-mitre-detection-strategy--new-ds-001', - target_ref: 'attack-pattern--new-ent-002', + source_ref: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', + target_ref: 'attack-pattern--22222222-2222-4222-8222-222222222222', description: 'Detection strategy detects enterprise technique 2', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_modified_by_ref: mitreIdentityId, }, // DEPRECATED 'detects' relationship: Data Component → Technique (should be IGNORED) { type: 'relationship', - id: 'relationship--new-dc-detects-tech-dep', + id: 'relationship--ffffffff-ffff-4fff-8fff-ffffffffffff', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', relationship_type: 'detects', - source_ref: 'x-mitre-data-component--new-dc-001', - target_ref: 'attack-pattern--new-ent-001', + source_ref: 'x-mitre-data-component--99999999-9999-4999-8999-999999999999', + target_ref: 'attack-pattern--11111111-1111-4111-8111-111111111111', description: 'DEPRECATED: Data component detects technique (should be ignored)', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_modified_by_ref: mitreIdentityId, }, // ======================================== @@ -437,11 +473,14 @@ const newSpecBundleData = { modified: '2017-06-01T00:00:00.000Z', name: 'The MITRE Corporation', identity_class: 'organization', + object_marking_refs: [markingDefinitionId], spec_version: '2.1', + x_mitre_attack_spec_version: config.app.attackSpecVersion, }, { type: 'marking-definition', id: markingDefinitionId, + created_by_ref: mitreIdentityId, created: '2017-06-01T00:00:00.000Z', definition_type: 'statement', definition: { @@ -465,8 +504,8 @@ describe('STIX Bundles New Specification API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the imported bundle fixture is ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -570,7 +609,7 @@ describe('STIX Bundles New Specification API', function () { // The new spec maintains a clean separation: deprecated patterns are excluded // even if both endpoints exist in the bundle as primary objects const deprecatedRelationship = stixBundle.objects.find( - (o) => o.id === 'relationship--new-dc-detects-tech-dep', + (o) => o.id === 'relationship--ffffffff-ffff-4fff-8fff-ffffffffffff', ); expect(deprecatedRelationship).toBeUndefined(); @@ -578,7 +617,7 @@ describe('STIX Bundles New Specification API', function () { const validDetectsRels = stixBundle.objects.filter( (o) => o.type === 'relationship' && o.relationship_type === 'detects', ); - expect(validDetectsRels.length).toBe(2); // Only DS-001 detects relationships + expect(validDetectsRels.length).toBe(2); // Only DET0001 detects relationships validDetectsRels.forEach((rel) => { expect(rel.source_ref).toMatch(/^x-mitre-detection-strategy--/); }); @@ -596,8 +635,10 @@ describe('STIX Bundles New Specification API', function () { const stixBundle = res.body; - // Verify DS-001 is included because it has 'detects' relationships to in-scope techniques - const ds001 = stixBundle.objects.find((o) => o.id === 'x-mitre-detection-strategy--new-ds-001'); + // Verify DET0001 is included because it has 'detects' relationships to in-scope techniques + const ds001 = stixBundle.objects.find( + (o) => o.id === 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', + ); expect(ds001).toBeDefined(); expect(ds001.name).toBe('Detection Strategy 1 - Detects Technique via Relationship'); expect(ds001.x_mitre_domains).toEqual([enterpriseDomain]); @@ -607,7 +648,7 @@ describe('STIX Bundles New Specification API', function () { (o) => o.type === 'relationship' && o.relationship_type === 'detects' && - o.source_ref === 'x-mitre-detection-strategy--new-ds-001', + o.source_ref === 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', ); expect(ds001DetectsRels.length).toBe(2); // Detects two techniques }); @@ -624,15 +665,21 @@ describe('STIX Bundles New Specification API', function () { const stixBundle = res.body; - // Verify DS-002 is included because it references an in-scope analytic via x_mitre_analytic_refs - const ds002 = stixBundle.objects.find((o) => o.id === 'x-mitre-detection-strategy--new-ds-002'); + // Verify DET0002 is included because it references an in-scope analytic via x_mitre_analytic_refs + const ds002 = stixBundle.objects.find( + (o) => o.id === 'x-mitre-detection-strategy--77777777-7777-4777-8777-777777777777', + ); expect(ds002).toBeDefined(); expect(ds002.name).toBe('Detection Strategy 2 - References Analytic'); - expect(ds002.x_mitre_analytic_refs).toContain('x-mitre-analytic--new-ana-001'); + expect(ds002.x_mitre_analytic_refs).toContain( + 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', + ); expect(ds002.x_mitre_domains).toEqual([enterpriseDomain]); // Verify the referenced analytic is in the bundle - const analytic = stixBundle.objects.find((o) => o.id === 'x-mitre-analytic--new-ana-001'); + const analytic = stixBundle.objects.find( + (o) => o.id === 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', + ); expect(analytic).toBeDefined(); }); @@ -648,8 +695,10 @@ describe('STIX Bundles New Specification API', function () { const stixBundle = res.body; - // Verify DS-003 is NOT included (orphaned - no technique or analytic reference) - const ds003 = stixBundle.objects.find((o) => o.id === 'x-mitre-detection-strategy--new-ds-003'); + // Verify DET0003 is NOT included (orphaned - no technique or analytic reference) + const ds003 = stixBundle.objects.find( + (o) => o.id === 'x-mitre-detection-strategy--88888888-8888-4888-8888-888888888888', + ); expect(ds003).toBeUndefined(); }); @@ -668,7 +717,7 @@ describe('STIX Bundles New Specification API', function () { const dataSources = stixBundle.objects.filter((o) => o.type === 'x-mitre-data-source'); expect(dataSources.length).toBe(1); // Only new-ds-src-001 (enterprise) - expect(dataSources[0].id).toBe('x-mitre-data-source--new-ds-src-001'); + expect(dataSources[0].id).toBe('x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'); }); it('GET /api/stix-bundles without includeDataSources excludes data sources', async function () { @@ -711,7 +760,7 @@ describe('STIX Bundles New Specification API', function () { // Only 1 technique should be in ICS (new-ics-001) const techniques = stixBundle.objects.filter((o) => o.type === 'attack-pattern'); expect(techniques.length).toBe(1); - expect(techniques[0].id).toBe('attack-pattern--new-ics-001'); + expect(techniques[0].id).toBe('attack-pattern--33333333-3333-4333-8333-333333333333'); // No analytics in ICS domain const analytics = stixBundle.objects.filter((o) => o.type === 'x-mitre-analytic'); @@ -720,7 +769,9 @@ describe('STIX Bundles New Specification API', function () { // Only ICS data component (new-dc-002) const dataComponents = stixBundle.objects.filter((o) => o.type === 'x-mitre-data-component'); expect(dataComponents.length).toBe(1); - expect(dataComponents[0].id).toBe('x-mitre-data-component--new-dc-002'); + expect(dataComponents[0].id).toBe( + 'x-mitre-data-component--aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + ); // No detection strategies (none detect ICS techniques or reference ICS analytics) const detectionStrategies = stixBundle.objects.filter( From e2a4f5e5f81b799ca583cb12752a05f331ad4df8 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:42:09 -0400 Subject: [PATCH 364/370] test(system-configuration): run system-configuration suites with ADM validation enabled Enable ADM request validation in both system-configuration specs. Use a valid ATT&CK tactic external reference in the organization-identity fixture. --- .../api/system-configuration/create-object-identity.spec.js | 6 +++--- .../api/system-configuration/system-configuration.spec.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/tests/api/system-configuration/create-object-identity.spec.js b/app/tests/api/system-configuration/create-object-identity.spec.js index 50ca8dc0..3942d785 100644 --- a/app/tests/api/system-configuration/create-object-identity.spec.js +++ b/app/tests/api/system-configuration/create-object-identity.spec.js @@ -22,7 +22,7 @@ const initialTacticData = { spec_version: '2.1', type: 'x-mitre-tactic', description: 'This is a tactic. yellow.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'TA9001' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], }, }; @@ -55,8 +55,8 @@ describe('Create Object with Organization Identity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/system-configuration/system-configuration.spec.js b/app/tests/api/system-configuration/system-configuration.spec.js index 7ff9f09d..99be4fdf 100644 --- a/app/tests/api/system-configuration/system-configuration.spec.js +++ b/app/tests/api/system-configuration/system-configuration.spec.js @@ -37,8 +37,8 @@ describe('System Configuration API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 166931024ebf63a4d5782426aabb8d2dc66fe72a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:45:48 -0400 Subject: [PATCH 365/370] test(tactics): run tactics suites with ADM validation enabled Enable ADM request validation in the tactics CRUD and tactics-techniques specs. Use valid ATT&CK tactic and technique external IDs, tactic shortnames, technique phase names, and subtechnique flags in the fixtures. Remove the unsupported marking-definition domain field from the bundle fixture. --- app/tests/api/tactics/tactics.spec.js | 6 +- app/tests/api/tactics/tactics.techniques.json | 85 +++++++++---------- .../api/tactics/tactics.techniques.spec.js | 12 ++- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index d810918b..217f7844 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -24,7 +24,7 @@ const initialObjectData = { spec_version: '2.1', type: 'x-mitre-tactic', description: 'This is a tactic. yellow.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'TA9001' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], }, }; @@ -41,8 +41,8 @@ describe('Tactics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/tactics/tactics.techniques.json b/app/tests/api/tactics/tactics.techniques.json index e93207cd..5c158855 100644 --- a/app/tests/api/tactics/tactics.techniques.json +++ b/app/tests/api/tactics/tactics.techniques.json @@ -84,21 +84,21 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "enlil" + "phase_name": "execution" }, { "kill_chain_name": "mitre-attack", - "phase_name": "nabu" + "phase_name": "persistence" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0001", - "external_id": "TX0001" + "url": "https://attack.mitre.org/techniques/T9001", + "external_id": "T9001" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -118,17 +118,17 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "nabu" + "phase_name": "persistence" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0002", - "external_id": "TX0002" + "url": "https://attack.mitre.org/techniques/T9002", + "external_id": "T9002" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -148,17 +148,17 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "enki" + "phase_name": "defense-evasion" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0003", - "external_id": "TX0003" + "url": "https://attack.mitre.org/techniques/T9003", + "external_id": "T9003" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -178,21 +178,21 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "enlil" + "phase_name": "execution" }, { "kill_chain_name": "mitre-attack", - "phase_name": "enki" + "phase_name": "defense-evasion" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0004", - "external_id": "TX0004" + "url": "https://attack.mitre.org/techniques/T9004", + "external_id": "T9004" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -212,21 +212,21 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "nanna-suen" + "phase_name": "collection" }, { "kill_chain_name": "mitre-attack", - "phase_name": "nabu" + "phase_name": "persistence" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0005", - "external_id": "TX0005" + "url": "https://attack.mitre.org/techniques/T9005", + "external_id": "T9005" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -241,15 +241,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0001", - "url": "https://attack.mitre.org/tactics/TAX0001" + "external_id": "TA9001", + "url": "https://attack.mitre.org/tactics/TA9001" } ], "id": "x-mitre-tactic--d932e995-5207-4347-88ec-b52b32762357", "modified": "2022-04-01T06:07:08.000Z", "name": "Enlil", "type": "x-mitre-tactic", - "x_mitre_shortname": "enlil", + "x_mitre_shortname": "execution", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -264,15 +264,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0002", - "url": "https://attack.mitre.org/tactics/TAX0002" + "external_id": "TA9002", + "url": "https://attack.mitre.org/tactics/TA9002" } ], "id": "x-mitre-tactic--953fd636-2af2-4cad-adc8-5d7903295dba", "modified": "2022-04-01T06:07:08.000Z", "name": "Enki", "type": "x-mitre-tactic", - "x_mitre_shortname": "enki", + "x_mitre_shortname": "defense-evasion", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -287,15 +287,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0003", - "url": "https://attack.mitre.org/tactics/TAX0003" + "external_id": "TA9003", + "url": "https://attack.mitre.org/tactics/TA9003" } ], "id": "x-mitre-tactic--c1768fcd-abe2-462f-95cc-bfedbc8c64c6", "modified": "2022-03-01T06:07:08.000Z", "name": "Ianna", "type": "x-mitre-tactic", - "x_mitre_shortname": "inanna", + "x_mitre_shortname": "discovery", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -310,15 +310,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0004", - "url": "https://attack.mitre.org/tactics/TAX0004" + "external_id": "TA9004", + "url": "https://attack.mitre.org/tactics/TA9004" } ], "id": "x-mitre-tactic--60cf8617-223d-47db-b15e-0cdf3c1d6f52", "modified": "2022-02-01T06:07:08.000Z", "name": "Nabu", "type": "x-mitre-tactic", - "x_mitre_shortname": "nabu", + "x_mitre_shortname": "persistence", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -333,15 +333,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0005", - "url": "https://attack.mitre.org/tactics/TAX0005" + "external_id": "TA9005", + "url": "https://attack.mitre.org/tactics/TA9005" } ], "id": "x-mitre-tactic--0b518521-aae3-4169-9f7a-cdf8455a2d14", "modified": "2022-01-01T06:07:08.000Z", "name": "Nanna-Suen", "type": "x-mitre-tactic", - "x_mitre_shortname": "nanna-suen", + "x_mitre_shortname": "collection", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -356,15 +356,15 @@ "external_references": [ { "source_name": "mitre-mobile-attack", - "external_id": "TAX0006", - "url": "https://attack.mitre.org/tactics/TAX0006" + "external_id": "TA9006", + "url": "https://attack.mitre.org/tactics/TA9006" } ], "id": "x-mitre-tactic--f5a8c28b-3002-4d5b-9571-3f68b2b57e29", "modified": "2022-09-09T01:02:03.000Z", "name": "Nabu", "type": "x-mitre-tactic", - "x_mitre_shortname": "nabu", + "x_mitre_shortname": "persistence", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -390,8 +390,7 @@ "created": "2020-01-01T00:00:00.000Z", "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "definition_type": "statement", - "spec_version": "2.1", - "x_mitre_domains": ["ics-attack"] + "spec_version": "2.1" } ] } diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index 7f28a49d..e13bc0a4 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -30,8 +30,8 @@ describe('Tactics with Techniques API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the imported bundle fixture is ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -64,8 +64,12 @@ describe('Tactics with Techniques API', function () { expect(Array.isArray(tactics)).toBe(true); expect(tactics.length).toBe(6); - tactic1 = tactics.find((t) => t.stix.x_mitre_shortname === 'enlil'); - tactic2 = tactics.find((t) => t.stix.x_mitre_shortname === 'nabu'); + tactic1 = tactics.find( + (t) => t.stix.id === 'x-mitre-tactic--d932e995-5207-4347-88ec-b52b32762357', + ); + tactic2 = tactics.find( + (t) => t.stix.id === 'x-mitre-tactic--60cf8617-223d-47db-b15e-0cdf3c1d6f52', + ); }); it('GET /api/techniques should return the preloaded techniques', async function () { From da1a0628f90aa543a0d934a4d4ae228d7ae4134c Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:48:11 -0400 Subject: [PATCH 366/370] test(teams): run teams suites with ADM validation enabled Enable ADM request validation in the teams specs for consistency. No fixture changes were required; teams use non-STIX payloads. --- app/tests/api/teams/teams-invalid.spec.js | 4 ++-- app/tests/api/teams/teams.spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/tests/api/teams/teams-invalid.spec.js b/app/tests/api/teams/teams-invalid.spec.js index fe168557..57e02108 100644 --- a/app/tests/api/teams/teams-invalid.spec.js +++ b/app/tests/api/teams/teams-invalid.spec.js @@ -30,8 +30,8 @@ describe('Teams API Test Invalid Data', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/teams/teams.spec.js b/app/tests/api/teams/teams.spec.js index d8c8359f..a56f9c05 100644 --- a/app/tests/api/teams/teams.spec.js +++ b/app/tests/api/teams/teams.spec.js @@ -51,8 +51,8 @@ describe('Teams API', function () { const user1 = new UserAccount(exampleUser); await user1.save(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From 31adf50e2596e6a032af8922f4cb14eadb314629 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:50:07 -0400 Subject: [PATCH 367/370] test(user-accounts): run user-accounts suites with ADM validation enabled Enable ADM request validation in the user-accounts specs for consistency. No fixture changes were required; user-accounts use non-STIX payloads. --- app/tests/api/user-accounts/user-accounts-invalid.spec.js | 4 ++-- app/tests/api/user-accounts/user-accounts.spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/tests/api/user-accounts/user-accounts-invalid.spec.js b/app/tests/api/user-accounts/user-accounts-invalid.spec.js index fe0a118e..d7d8a155 100644 --- a/app/tests/api/user-accounts/user-accounts-invalid.spec.js +++ b/app/tests/api/user-accounts/user-accounts-invalid.spec.js @@ -40,8 +40,8 @@ describe('User Accounts API Test Invalid Data', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index 27e7c059..8b254e23 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -38,8 +38,8 @@ describe('User Accounts API', function () { await UserAccount.init(); await Team.init(); - // Disable validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app From fd7153eaea721180a8d2e15863dfd450981f732a Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:55:39 -0400 Subject: [PATCH 368/370] test(attack-objects): run attack-objects suites with ADM validation enabled Enable ADM request validation in the attack-objects API spec and pin ADM validation in pagination. Normalize attack-object import fixtures to use valid ATT&CK IDs, required technique data sources, subtechnique flags, full-schema metadata, and valid software platforms. Remove unsupported marking-definition domains from the import bundles. --- .../api/attack-objects/attack-objects-1.json | 82 +++++++++++-------- .../api/attack-objects/attack-objects-2.json | 10 +-- .../attack-objects-pagination.spec.js | 10 +-- .../api/attack-objects/attack-objects.spec.js | 22 +++-- 4 files changed, 69 insertions(+), 55 deletions(-) diff --git a/app/tests/api/attack-objects/attack-objects-1.json b/app/tests/api/attack-objects/attack-objects-1.json index ca745e3c..74cab74e 100644 --- a/app/tests/api/attack-objects/attack-objects-1.json +++ b/app/tests/api/attack-objects/attack-objects-1.json @@ -110,11 +110,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0001", - "external_id": "TX0001" + "url": "https://attack.mitre.org/techniques/T9001", + "external_id": "T9001" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -140,11 +141,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0002", - "external_id": "TX0002" + "url": "https://attack.mitre.org/techniques/T9002", + "external_id": "T9002" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -170,11 +172,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0003", - "external_id": "TX0003" + "url": "https://attack.mitre.org/techniques/T9003", + "external_id": "T9003" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -204,11 +207,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0004", - "external_id": "TX0004" + "url": "https://attack.mitre.org/techniques/T9004", + "external_id": "T9004" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -238,11 +242,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0005", - "external_id": "TX0005" + "url": "https://attack.mitre.org/techniques/T9005", + "external_id": "T9005" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -257,8 +262,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0001", - "url": "https://attack.mitre.org/tactics/TAX0001" + "external_id": "TA9001", + "url": "https://attack.mitre.org/tactics/TA9001" } ], "id": "x-mitre-tactic--d932e995-5207-4347-88ec-b52b32762357", @@ -280,8 +285,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0002", - "url": "https://attack.mitre.org/tactics/TAX0002" + "external_id": "TA9002", + "url": "https://attack.mitre.org/tactics/TA9002" } ], "id": "x-mitre-tactic--953fd636-2af2-4cad-adc8-5d7903295dba", @@ -303,8 +308,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0003", - "url": "https://attack.mitre.org/tactics/TAX0003" + "external_id": "TA9003", + "url": "https://attack.mitre.org/tactics/TA9003" } ], "id": "x-mitre-tactic--c1768fcd-abe2-462f-95cc-bfedbc8c64c6", @@ -326,8 +331,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0004", - "url": "https://attack.mitre.org/tactics/TAX0004" + "external_id": "TA9004", + "url": "https://attack.mitre.org/tactics/TA9004" } ], "id": "x-mitre-tactic--60cf8617-223d-47db-b15e-0cdf3c1d6f52", @@ -349,8 +354,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0005", - "url": "https://attack.mitre.org/tactics/TAX0005" + "external_id": "TA9005", + "url": "https://attack.mitre.org/tactics/TA9005" } ], "id": "x-mitre-tactic--0b518521-aae3-4169-9f7a-cdf8455a2d14", @@ -372,8 +377,8 @@ "external_references": [ { "source_name": "mitre-mobile-attack", - "external_id": "TAX0006", - "url": "https://attack.mitre.org/tactics/TAX0006" + "external_id": "TA9006", + "url": "https://attack.mitre.org/tactics/TA9006" } ], "id": "x-mitre-tactic--f5a8c28b-3002-4d5b-9571-3f68b2b57e29", @@ -396,8 +401,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "SX3333", - "url": "https://attack.mitre.org/software/SX3333" + "external_id": "S9001", + "url": "https://attack.mitre.org/software/S9001" }, { "source_name": "source-1", "external_id": "s1" } ], @@ -405,9 +410,10 @@ "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.1", "x_mitre_aliases": ["software-1"], - "x_mitre_platforms": ["platform-1"], + "x_mitre_platforms": ["Android"], "x_mitre_contributors": ["contributor-1", "contributor-2"], "x_mitre_domains": ["mobile-attack"], + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -420,13 +426,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "GX1111", - "url": "https://attack.mitre.org/groups/GX1111" + "external_id": "G9001", + "url": "https://attack.mitre.org/groups/G9001" }, { "source_name": "source-1", "external_id": "s1" } ], "object_marking_refs": ["marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce"], "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", + "x_mitre_version": "1.1", + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -439,14 +447,16 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "T9999", - "url": "https://attack.mitre.org/mitigations/T9999" + "external_id": "M9001", + "url": "https://attack.mitre.org/mitigations/M9001" }, { "source_name": "source-1", "external_id": "s1" } ], "object_marking_refs": ["marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce"], "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.1", + "x_mitre_domains": ["enterprise-attack"], + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -459,6 +469,7 @@ "target_ref": "attack-pattern--757471d4-d931-4109-82dd-cdd50c04744e", "object_marking_refs": ["marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce"], "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -482,8 +493,7 @@ "created": "2020-01-01T00:00:00.000Z", "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "definition_type": "statement", - "spec_version": "2.1", - "x_mitre_domains": ["ics-attack"] + "spec_version": "2.1" } ] } diff --git a/app/tests/api/attack-objects/attack-objects-2.json b/app/tests/api/attack-objects/attack-objects-2.json index ae13f878..cc065b43 100644 --- a/app/tests/api/attack-objects/attack-objects-2.json +++ b/app/tests/api/attack-objects/attack-objects-2.json @@ -54,11 +54,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0001", - "external_id": "TX0001" + "url": "https://attack.mitre.org/techniques/T9001", + "external_id": "T9001" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -84,8 +85,7 @@ "created": "2020-01-01T00:00:00.000Z", "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "definition_type": "statement", - "spec_version": "2.1", - "x_mitre_domains": ["ics-attack"] + "spec_version": "2.1" } ] } diff --git a/app/tests/api/attack-objects/attack-objects-pagination.spec.js b/app/tests/api/attack-objects/attack-objects-pagination.spec.js index 4a8cf0ff..206320fa 100644 --- a/app/tests/api/attack-objects/attack-objects-pagination.spec.js +++ b/app/tests/api/attack-objects/attack-objects-pagination.spec.js @@ -13,15 +13,12 @@ const initialObjectData = { spec_version: '2.1', type: 'attack-pattern', description: 'This is a technique.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], - x_mitre_data_sources: ['data-source-1', 'data-source-2'], + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, - x_mitre_impact_type: ['impact-1'], - x_mitre_platforms: ['platform-1', 'platform-2'], + x_mitre_platforms: ['Linux', 'macOS'], }, }; @@ -32,6 +29,9 @@ const options = { baseUrl: '/api/attack-objects', label: 'Attack Objects', state: 'work-in-progress', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(techniquesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index 3724b6ba..287d8bc4 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -28,8 +28,12 @@ const malwareObject = { description: 'This is a malware type of software, with a URL that it should not have (https://attack.mitre.org/software/SW0001)', is_family: false, + external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', + x_mitre_aliases: ['software-2'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-mk', 'contributor-cm'], x_mitre_domains: ['mobile-attack'], created: '2023-03-01T00:00:00.000Z', @@ -57,8 +61,8 @@ describe('ATT&CK Objects API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -156,9 +160,9 @@ describe('ATT&CK Objects API', function () { expect(attackObjects.length).toBe(0); }); - it('GET /api/attack-objects returns the group with ATT&CK ID GX1111', async function () { + it('GET /api/attack-objects returns the group with ATT&CK ID G9001', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=GX1111') + .get('/api/attack-objects?attackId=G9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -171,9 +175,9 @@ describe('ATT&CK Objects API', function () { expect(attackObjects.length).toBe(1); }); - it('GET /api/attack-objects returns the software with ATT&CK ID SX3333', async function () { + it('GET /api/attack-objects returns the software with ATT&CK ID S9001', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=SX3333') + .get('/api/attack-objects?attackId=S9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -186,9 +190,9 @@ describe('ATT&CK Objects API', function () { expect(attackObjects.length).toBe(1); }); - it('GET /api/attack-objects returns the technique with ATT&CK ID TX0001', async function () { + it('GET /api/attack-objects returns the technique with ATT&CK ID T9001', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=TX0001') + .get('/api/attack-objects?attackId=T9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -203,7 +207,7 @@ describe('ATT&CK Objects API', function () { it('GET /api/attack-objects returns the objects with the requested ATT&CK IDs', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=GX1111&attackId=SX3333&attackId=TX0001') + .get('/api/attack-objects?attackId=G9001&attackId=S9001&attackId=T9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) From fe9c91feb768699a4e4dbf842f96493861cf473f Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:01:59 -0400 Subject: [PATCH 369/370] test(collection-bundles): run collection-bundles suites with ADM validation enabled Enable ADM request validation in the collection-bundles basic and streaming specs. Normalize reusable bundle fixtures with valid ATT&CK external references, domains, platforms, data source formats, collection metadata, and group alias ordering. Update import error-count assertions for the intentionally malformed missing-spec-version fixture now that ADM records that validation error. --- .../collection-bundles.spec.js | 114 +++++++++++++++++- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/app/tests/api/collection-bundles/collection-bundles.spec.js b/app/tests/api/collection-bundles/collection-bundles.spec.js index d3928f72..4ae586a5 100644 --- a/app/tests/api/collection-bundles/collection-bundles.spec.js +++ b/app/tests/api/collection-bundles/collection-bundles.spec.js @@ -430,6 +430,108 @@ const collectionData6 = { }, }; +const attackIdsByObjectId = new Map([ + ['attack-pattern--2204c371-6100-4ae0-82f3-25c07c29772a', 'T9001'], + ['attack-pattern--82f04b1e-5371-4a6f-be06-411f0f43b483', 'T9002'], + ['attack-pattern--14fbfb6a-c4d9-4c3b-a7ef-f8df23e3b22b', 'T9003'], + ['attack-pattern--fb4f094c-ad39-4dba-b459-5e314f6d6c8d', 'T9004'], + ['attack-pattern--44fc382e-0b71-4f5d-9110-fb2e35452d98', 'T9005'], + ['course-of-action--25dc1ce8-eb55-4333-ae30-a7cb4f5894a1', 'M9001'], + ['course-of-action--e944670c-d03a-4e93-a21c-b3d4c53ec4c9', 'M9002'], + ['malware--04227b24-7817-4de1-9050-b7b1b57f5866', 'S9001'], + ['intrusion-set--d69e568e-9ac8-4c08-b32c-d93b43ba9172', 'G9001'], +]); + +function setAttackExternalReference(stixObject, sourceName, externalId, urlSegment) { + const existingReferences = Array.isArray(stixObject.external_references) + ? stixObject.external_references.slice(1) + : []; + + stixObject.external_references = [ + { + source_name: sourceName, + external_id: externalId, + url: `https://attack.mitre.org/${urlSegment}/${externalId}`, + }, + ...existingReferences, + ]; +} + +function normalizeBundleObjectForAdm(stixObject) { + if (stixObject.type === 'x-mitre-collection') { + stixObject.x_mitre_version = stixObject.x_mitre_version || '1.0'; + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + return; + } + + if (stixObject.type === 'attack-pattern') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'techniques'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['enterprise-attack']; + stixObject.kill_chain_phases = [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }]; + stixObject.x_mitre_data_sources = ['Process: Process Creation']; + stixObject.x_mitre_platforms = ['Linux']; + delete stixObject.x_mitre_impact_type; + return; + } + + if (stixObject.type === 'course-of-action') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'mitigations'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['enterprise-attack']; + return; + } + + if (stixObject.type === 'malware') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'software'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['mobile-attack']; + stixObject.x_mitre_platforms = ['Android']; + stixObject.is_family = false; + return; + } + + if (stixObject.type === 'intrusion-set') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'groups'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['enterprise-attack']; + stixObject.aliases = [ + stixObject.name, + ...(stixObject.aliases || []).filter((alias) => alias !== stixObject.name), + ]; + } +} + +function normalizeBundleForAdm(bundle) { + for (const stixObject of bundle.objects) { + normalizeBundleObjectForAdm(stixObject); + } +} + +normalizeBundleForAdm(collectionBundleData); +normalizeBundleForAdm(collectionBundleData2); +normalizeBundleForAdm(collectionBundleData4); +normalizeBundleForAdm(collectionBundleData5); +collectionData6.stix.x_mitre_version = '1.0'; +collectionData6.stix.x_mitre_attack_spec_version = currentAttackSpecVersion; + describe('Collection Bundles Basic API', function () { let app; let passportCookie; @@ -442,8 +544,8 @@ describe('Collection Bundles Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; reusable valid fixture objects are normalized for ADM below + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -572,7 +674,7 @@ describe('Collection Bundles Basic API', function () { collection1 = response.body; expect(collection1).toBeDefined(); expect(collection1.workspace.import_categories.additions.length).toBe(8); - expect(collection1.workspace.import_categories.errors.length).toBe(4); + expect(collection1.workspace.import_categories.errors.length).toBe(5); }); it('POST /api/collection-bundles does not show a successful preview with a duplicate collection bundle', async function () { @@ -625,7 +727,7 @@ describe('Collection Bundles Basic API', function () { expect(collection2).toBeDefined(); expect(collection2.workspace.import_categories.changes.length).toBe(1); expect(collection2.workspace.import_categories.duplicates.length).toBe(6); - expect(collection2.workspace.import_categories.errors.length).toBe(4); + expect(collection2.workspace.import_categories.errors.length).toBe(5); }); it('GET /api/references returns the malware added reference', async function () { @@ -824,6 +926,10 @@ describe('Collection Bundles Streaming API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Enable ADM validation; reusable valid fixture objects are normalized for ADM below + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); From bd32292a6db8a198234a1d4e73023142fcb8be82 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 22 Jun 2026 18:03:30 -0400 Subject: [PATCH 370/370] test(collection-indexes): run collection-indexes suites with ADM validation enabled Enable ADM request validation in the collection-indexes spec for consistency. No fixture changes were required; collection indexes use non-STIX payloads. --- app/tests/api/collection-indexes/collection-indexes.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/api/collection-indexes/collection-indexes.spec.js b/app/tests/api/collection-indexes/collection-indexes.spec.js index 4d99cb03..7e29d610 100644 --- a/app/tests/api/collection-indexes/collection-indexes.spec.js +++ b/app/tests/api/collection-indexes/collection-indexes.spec.js @@ -74,8 +74,8 @@ describe('Collection Indexes Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app