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 01/30] 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 02/30] 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 03/30] 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 04/30] 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 05/30] 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 06/30] 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 07/30] 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 08/30] 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 09/30] 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 10/30] 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 11/30] 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 12/30] 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 13/30] 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 14/30] 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 15/30] 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 16/30] 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 17/30] 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 18/30] 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 19/30] 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 20/30] 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 21/30] 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 22/30] 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 23/30] 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 24/30] 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 25/30] 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 26/30] 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 27/30] 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 28/30] 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 29/30] 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 30/30] 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